Compare commits
13 Commits
4e56ea3435
...
v5
| Author | SHA1 | Date | |
|---|---|---|---|
| 7e92e35991 | |||
| c0bb86bace | |||
| 427c488cd6 | |||
| a6c4cc2173 | |||
| ddc782dc76 | |||
| 1b9367dcbb | |||
| 12cf94a9dc | |||
| 16b21ac243 | |||
| 9bd5e340b0 | |||
| cd219ae00b | |||
| 78e67c2aab | |||
| c3a9a6b19c | |||
| 562f7e9e37 |
@@ -0,0 +1,78 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
<CentralPackageFloatingVersionsEnabled>true</CentralPackageFloatingVersionsEnabled>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Avalonia" Version="11.2.*" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.2.*" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.2.*" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.2.*" />
|
||||
<PackageVersion Include="Avalonia.Fonts.Inter" Version="11.2.*" />
|
||||
<PackageVersion Include="Avalonia.Headless.XUnit" Version="11.2.*" />
|
||||
<PackageVersion Include="Avalonia.Themes.Fluent" Version="11.2.*" />
|
||||
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageVersion Include="Cronos" Version="0.11.1" />
|
||||
<PackageVersion Include="Dapper" Version="2.1.66" />
|
||||
<PackageVersion Include="dbup-sqlserver" Version="6.0.16" />
|
||||
<PackageVersion Include="DiffPlex" Version="1.7.*" />
|
||||
<PackageVersion Include="LdapForNet" Version="2.7.15" />
|
||||
<PackageVersion Include="MessageBox.Avalonia" Version="3.1.*" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Data.SqlClient" Version="6.1.3" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options.DataAnnotations" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageVersion Include="Microsoft.Playwright" Version="1.51.0" />
|
||||
<PackageVersion Include="NPOI" Version="2.7.5" />
|
||||
<PackageVersion Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageVersion Include="OneOf" Version="3.0.271" />
|
||||
<PackageVersion Include="OneOf.SourceGenerator" Version="3.0.271" />
|
||||
<PackageVersion Include="Oracle.ManagedDataAccess.Core" Version="23.26.0" />
|
||||
<PackageVersion Include="protobuf-net-data" Version="4.1.0" />
|
||||
<PackageVersion Include="Radzen.Blazor" Version="8.4.2" />
|
||||
<PackageVersion Include="RichardSzalay.MockHttp" Version="7.0.0" />
|
||||
<PackageVersion Include="SecureStore" Version="1.2.0" />
|
||||
<PackageVersion Include="Serilog" Version="4.*" />
|
||||
<PackageVersion Include="Serilog.Extensions.Logging" Version="8.0.*" />
|
||||
<PackageVersion Include="Serilog.Sinks.Console" Version="6.*" />
|
||||
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.*" />
|
||||
<PackageVersion Include="Shouldly" Version="4.3.0" />
|
||||
<PackageVersion Include="SqlKata" Version="3.2.3" />
|
||||
<PackageVersion Include="SqlKata.Execution" Version="3.2.3" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="7.3.1" />
|
||||
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
||||
<PackageVersion Include="System.DirectoryServices.Protocols" Version="10.0.1" />
|
||||
<PackageVersion Include="xunit" Version="2.9.3" />
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.4" />
|
||||
<PackageVersion Include="ZstdSharp.Port" Version="0.8.1" />
|
||||
|
||||
<PackageVersion Update="Dapper" Version="2.1.35" Condition="'$(MSBuildProjectName)' == 'JdeScoping.Database.Tests'" />
|
||||
<PackageVersion Update="Microsoft.Data.SqlClient" Version="5.2.2" Condition="'$(MSBuildProjectName)' == 'JdeScoping.Database.Tests'" />
|
||||
<PackageVersion Update="Microsoft.NET.Test.Sdk" Version="17.12.0" Condition="'$(MSBuildProjectName)' == 'JdeScoping.Api.IntegrationTests' or '$(MSBuildProjectName)' == 'JdeScoping.Api.Tests' or '$(MSBuildProjectName)' == 'JdeScoping.ConfigManager.Cli.Tests' or '$(MSBuildProjectName)' == 'JdeScoping.ConfigManager.Core.Tests' or '$(MSBuildProjectName)' == 'JdeScoping.ConfigManager.Ui.Tests'" />
|
||||
<PackageVersion Update="Shouldly" Version="4.2.1" Condition="'$(MSBuildProjectName)' == 'JdeScoping.Api.IntegrationTests' or '$(MSBuildProjectName)' == 'JdeScoping.Api.Tests' or '$(MSBuildProjectName)' == 'JdeScoping.ConfigManager.Cli.Tests' or '$(MSBuildProjectName)' == 'JdeScoping.ConfigManager.Core.Tests' or '$(MSBuildProjectName)' == 'JdeScoping.ConfigManager.Ui.Tests'" />
|
||||
<PackageVersion Update="xunit.runner.visualstudio" Version="3.0.1" Condition="'$(MSBuildProjectName)' == 'JdeScoping.Api.IntegrationTests' or '$(MSBuildProjectName)' == 'JdeScoping.Api.Tests' or '$(MSBuildProjectName)' == 'JdeScoping.ConfigManager.Cli.Tests' or '$(MSBuildProjectName)' == 'JdeScoping.ConfigManager.Core.Tests' or '$(MSBuildProjectName)' == 'JdeScoping.ConfigManager.Ui.Tests'" />
|
||||
<PackageVersion Update="xunit.runner.visualstudio" Version="3.0.2" Condition="'$(MSBuildProjectName)' == 'JdeScoping.DataAccess.Tests' or '$(MSBuildProjectName)' == 'JdeScoping.DataSync.Dev.Tests' or '$(MSBuildProjectName)' == 'JdeScoping.DataSync.Tests' or '$(MSBuildProjectName)' == 'JdeScoping.ExcelIO.Tests'" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -15,6 +15,7 @@
|
||||
<Project Path="src/Utils/JdeScoping.ConfigManager.Core/JdeScoping.ConfigManager.Core.csproj" />
|
||||
<Project Path="src/Utils/JdeScoping.ConfigManager.Cli/JdeScoping.ConfigManager.Cli.csproj" />
|
||||
<Project Path="src/Utils/JdeScoping.ConfigManager.Ui/JdeScoping.ConfigManager.Ui.csproj" />
|
||||
<Project Path="src/Utils/JdeScoping.DevLoader/JdeScoping.DevLoader.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/tests/">
|
||||
<Project Path="tests/JdeScoping.Api.Tests/JdeScoping.Api.Tests.csproj" />
|
||||
@@ -28,6 +29,7 @@
|
||||
<Project Path="tests/JdeScoping.ExcelIO.Tests/JdeScoping.ExcelIO.Tests.csproj" />
|
||||
<Project Path="tests/JdeScoping.Host.Tests/JdeScoping.Host.Tests.csproj" />
|
||||
<Project Path="tests/JdeScoping.Infrastructure.Tests/JdeScoping.Infrastructure.Tests.csproj" />
|
||||
<Project Path="tests/JdeScoping.Ui.Tests/JdeScoping.Ui.Tests.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/tests/utils/">
|
||||
<Project Path="tests/Utils/JdeScoping.ConfigManager.Core.Tests/JdeScoping.ConfigManager.Core.Tests.csproj" />
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"LotFinder": "Server=host.docker.internal,1434;Database=ScopingTool;User Id=scopingapp;Password=Sc0ping@pp_Dev#2024;TrustServerCertificate=true",
|
||||
"LotFinderDB": "Server=host.docker.internal,1434;Database=ScopingTool;User Id=scopingapp;Password=Sc0ping@pp_Dev#2024;TrustServerCertificate=true",
|
||||
"SqlServer": "Server=host.docker.internal,1434;Database=ScopingTool;User Id=scopingapp;Password=Sc0ping@pp_Dev#2024;TrustServerCertificate=true",
|
||||
"LotFinder": "Server=host.docker.internal,1434;Database=ScopingTool;User Id=scopingapp;Password=Sc0ping@pp_Dev#2024;Encrypt=false;TrustServerCertificate=true",
|
||||
"LotFinderDB": "Server=host.docker.internal,1434;Database=ScopingTool;User Id=scopingapp;Password=Sc0ping@pp_Dev#2024;Encrypt=false;TrustServerCertificate=true",
|
||||
"SqlServer": "Server=host.docker.internal,1434;Database=ScopingTool;User Id=scopingapp;Password=Sc0ping@pp_Dev#2024;Encrypt=false;TrustServerCertificate=true",
|
||||
"GIW": "Data Source=localhost:1521/GIW;User Id=placeholder;Password=placeholder"
|
||||
},
|
||||
"DataAccess": {
|
||||
|
||||
@@ -73,6 +73,7 @@ public class ManualSyncController : ApiControllerBase
|
||||
public ActionResult<List<PipelineInfoViewModel>> GetPipelines()
|
||||
{
|
||||
var pipelines = _pipelineRegistry.GetEnabledPipelines()
|
||||
.OrderBy(p => p.Name)
|
||||
.Select(p => new PipelineInfoViewModel
|
||||
{
|
||||
Name = p.Name,
|
||||
|
||||
@@ -39,33 +39,33 @@ public class RefreshStatusController : ApiControllerBase
|
||||
[FromQuery] DateTime maxDT,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// Get raw data updates from repository
|
||||
var updates = await _repository.GetLastDataUpdatesAsync(ct);
|
||||
// Get data updates filtered in SQL by date range (end of day for maxDT)
|
||||
var updates = await _repository.GetDataUpdatesInRangeAsync(minDT, maxDT.Date.AddDays(1), ct);
|
||||
|
||||
// Filter by date range
|
||||
var filtered = updates
|
||||
.Where(u => u.StartDt >= minDT && u.StartDt <= maxDT.AddDays(1))
|
||||
.ToList();
|
||||
|
||||
// Group by StartDt (rounded to minute) to aggregate multiple table updates into single rows
|
||||
var aggregated = filtered
|
||||
// Group by StartDt (rounded to minute) to aggregate multiple table updates into single rows.
|
||||
// Within each run, deduplicate by TableName so each table appears once even when
|
||||
// multiple UpdateTypes (Hourly/Daily/Mass) ran in the same minute.
|
||||
var aggregated = updates
|
||||
.GroupBy(u => new DateTime(u.StartDt.Year, u.StartDt.Month, u.StartDt.Day, u.StartDt.Hour, u.StartDt.Minute, 0))
|
||||
.Select(g => new DataUpdateDto
|
||||
.Select(g =>
|
||||
{
|
||||
StartDt = g.Key,
|
||||
EndDt = g.Max(u => u.EndDt),
|
||||
WasSuccessful = g.All(u => u.WasSuccessful),
|
||||
BranchRecords = (int)(g.FirstOrDefault(u => u.TableName == "Branch")?.NumberRecords ?? 0),
|
||||
ProfitCenterRecords = (int)(g.FirstOrDefault(u => u.TableName == "ProfitCenter")?.NumberRecords ?? 0),
|
||||
WorkCenterRecords = (int)(g.FirstOrDefault(u => u.TableName == "WorkCenter")?.NumberRecords ?? 0),
|
||||
OrgHierarchyRecords = (int)(g.FirstOrDefault(u => u.TableName == "OrgHierarchy")?.NumberRecords ?? 0),
|
||||
StatusCodeRecords = (int)(g.FirstOrDefault(u => u.TableName == "StatusCode")?.NumberRecords ?? 0),
|
||||
UserRecords = (int)(g.FirstOrDefault(u => u.TableName == "JdeUser")?.NumberRecords ?? 0),
|
||||
ItemRecords = (int)(g.FirstOrDefault(u => u.TableName == "Item")?.NumberRecords ?? 0),
|
||||
LotRecords = (int)(g.FirstOrDefault(u => u.TableName == "Lot")?.NumberRecords ?? 0),
|
||||
WorkOrderRecords = (int)(g.FirstOrDefault(u => u.TableName.Contains("WorkOrder_"))?.NumberRecords ?? 0),
|
||||
WorkOrderStepRecords = (int)(g.FirstOrDefault(u => u.TableName.Contains("WorkOrderStep"))?.NumberRecords ?? 0),
|
||||
WorkOrderComponentRecords = (int)(g.FirstOrDefault(u => u.TableName.Contains("WorkOrderComponent"))?.NumberRecords ?? 0)
|
||||
var items = g.GroupBy(u => u.TableName)
|
||||
.Select(tg => new DataUpdateItemDto
|
||||
{
|
||||
TableName = tg.Key,
|
||||
WasSuccessful = tg.All(u => u.WasSuccessful),
|
||||
NumberRecords = tg.Sum(u => Math.Max(0, u.NumberRecords))
|
||||
}).OrderBy(i => i.TableName).ToList();
|
||||
|
||||
return new DataUpdateDto
|
||||
{
|
||||
StartDt = g.Key,
|
||||
EndDt = g.Max(u => u.EndDt),
|
||||
WasSuccessful = items.All(i => i.WasSuccessful),
|
||||
PassedCount = items.Count(i => i.WasSuccessful),
|
||||
FailedCount = items.Count(i => !i.WasSuccessful),
|
||||
Items = items
|
||||
};
|
||||
})
|
||||
.OrderByDescending(d => d.StartDt)
|
||||
.ToList();
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.3.1" />
|
||||
<PackageReference Include="System.DirectoryServices.Protocols" Version="10.0.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" />
|
||||
<PackageReference Include="System.DirectoryServices.Protocols" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -34,20 +34,15 @@ public abstract class AutocompleteFilterPanelBase<TItem> : ComponentBase where T
|
||||
[Parameter]
|
||||
public bool IsReadOnly { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current search text.
|
||||
/// </summary>
|
||||
protected string SearchText { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// The search results from the API.
|
||||
/// </summary>
|
||||
protected List<TItem> SearchResults { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The currently selected item from search results.
|
||||
/// The currently selected value from the dropdown.
|
||||
/// </summary>
|
||||
protected TItem? SelectedItem { get; set; }
|
||||
protected TItem? SelectedValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reference to the data grid for explicit refresh.
|
||||
@@ -93,13 +88,6 @@ public abstract class AutocompleteFilterPanelBase<TItem> : ComponentBase where T
|
||||
/// <returns>The unique key value.</returns>
|
||||
protected abstract object GetItemKey(TItem item);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display text value for an item (used for matching in autocomplete).
|
||||
/// </summary>
|
||||
/// <param name="item">The item to get the display text for.</param>
|
||||
/// <returns>The display text value.</returns>
|
||||
protected abstract string GetDisplayText(TItem item);
|
||||
|
||||
/// <summary>
|
||||
/// Handles the autocomplete search.
|
||||
/// </summary>
|
||||
@@ -107,7 +95,7 @@ public abstract class AutocompleteFilterPanelBase<TItem> : ComponentBase where T
|
||||
/// <returns>A task representing the asynchronous search operation.</returns>
|
||||
protected async Task OnSearchAsync(LoadDataArgs args)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(args.Filter) && args.Filter.Length >= 3)
|
||||
if (!string.IsNullOrEmpty(args.Filter) && args.Filter.Length >= 2)
|
||||
{
|
||||
SearchResults = await SearchApiAsync(args.Filter);
|
||||
}
|
||||
@@ -117,40 +105,23 @@ public abstract class AutocompleteFilterPanelBase<TItem> : ComponentBase where T
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles selection from the autocomplete.
|
||||
/// </summary>
|
||||
/// <param name="value">The selected value from the autocomplete control.</param>
|
||||
protected void OnItemSelected(object value)
|
||||
{
|
||||
if (value is string text && !string.IsNullOrEmpty(text))
|
||||
{
|
||||
SelectedItem = SearchResults.FirstOrDefault(i => GetDisplayText(i) == text);
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedItem = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the selected item to the list.
|
||||
/// </summary>
|
||||
protected async Task AddItemAsync()
|
||||
{
|
||||
if (SelectedItem != null)
|
||||
if (SelectedValue != null)
|
||||
{
|
||||
var selectedKey = GetItemKey(SelectedItem);
|
||||
var selectedKey = GetItemKey(SelectedValue);
|
||||
var isDuplicate = Items.Any(i => GetItemKey(i).Equals(selectedKey));
|
||||
|
||||
if (!isDuplicate)
|
||||
{
|
||||
Items.Add(SelectedItem);
|
||||
Items.Add(SelectedValue);
|
||||
await ItemsChanged.InvokeAsync(Items);
|
||||
}
|
||||
}
|
||||
SearchText = "";
|
||||
SelectedItem = null;
|
||||
SelectedValue = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -24,19 +24,17 @@
|
||||
|
||||
@if (!IsReadOnly)
|
||||
{
|
||||
<RadzenRow Gap="0.5rem" class="rz-mb-3">
|
||||
<RadzenColumn Size="10">
|
||||
<RadzenFormField Text="Item Number" Style="width: 100%;">
|
||||
<RadzenAutoComplete @bind-Value="_searchText" Data="@_searchResults" TextProperty="ItemNumber"
|
||||
LoadData="@OnSearchAsync" MinLength="3" Placeholder="Search items (3+ chars)..."
|
||||
Style="width: 100%;" Change="@OnItemSelected" />
|
||||
</RadzenFormField>
|
||||
</RadzenColumn>
|
||||
<RadzenColumn Size="2">
|
||||
<RadzenButton Text="Add" Icon="add" ButtonStyle="ButtonStyle.Primary" Click="@AddItemAsync"
|
||||
Disabled="@(_selectedItem == null)" Style="margin-top: 24px;" />
|
||||
</RadzenColumn>
|
||||
</RadzenRow>
|
||||
<div class="filter-input-row rz-mb-3">
|
||||
<div class="filter-input-col">
|
||||
<label class="field-label">Item Number</label>
|
||||
<RadzenDropDown @bind-Value="_selectedValue" Data="@_searchResults" TextProperty="ItemNumber"
|
||||
LoadData="@OnSearchAsync" FilterCaseSensitivity="FilterCaseSensitivity.CaseInsensitive"
|
||||
AllowFiltering="true" AllowClear="true"
|
||||
Placeholder="Search items (2+ chars)..." Style="width: 100%;" />
|
||||
</div>
|
||||
<RadzenButton Text="Add" Icon="add" ButtonStyle="ButtonStyle.Primary" Click="@AddItemAsync"
|
||||
Disabled="@(_selectedValue == null)" />
|
||||
</div>
|
||||
}
|
||||
|
||||
<RadzenDataGrid @ref="_grid" Data="@Items" TItem="ItemViewModel" AllowSorting="true" Style="min-height: 150px; max-height: 300px;">
|
||||
@@ -72,9 +70,8 @@
|
||||
[Inject]
|
||||
private IJSRuntime JSRuntime { get; set; } = default!;
|
||||
|
||||
private string _searchText = "";
|
||||
private List<ItemViewModel> _searchResults = [];
|
||||
private ItemViewModel? _selectedItem;
|
||||
private ItemViewModel? _selectedValue;
|
||||
private bool _isUploading;
|
||||
private RadzenDataGrid<ItemViewModel>? _grid;
|
||||
|
||||
@@ -98,27 +95,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
private void OnItemSelected(object value)
|
||||
{
|
||||
if (value is string text && !string.IsNullOrEmpty(text))
|
||||
{
|
||||
_selectedItem = _searchResults.FirstOrDefault(i => i.ItemNumber == text);
|
||||
}
|
||||
else
|
||||
{
|
||||
_selectedItem = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AddItemAsync()
|
||||
{
|
||||
if (_selectedItem != null && !Items.Any(i => i.ItemNumber == _selectedItem.ItemNumber))
|
||||
if (_selectedValue != null && !Items.Any(i => i.ItemNumber == _selectedValue.ItemNumber))
|
||||
{
|
||||
Items.Add(_selectedItem);
|
||||
Items.Add(_selectedValue);
|
||||
await ItemsChanged.InvokeAsync(Items);
|
||||
}
|
||||
_searchText = "";
|
||||
_selectedItem = null;
|
||||
_selectedValue = null;
|
||||
}
|
||||
|
||||
private async Task DeleteItem(ItemViewModel item)
|
||||
|
||||
@@ -15,19 +15,17 @@
|
||||
|
||||
@if (!IsReadOnly)
|
||||
{
|
||||
<RadzenRow Gap="0.5rem" class="rz-mb-3">
|
||||
<RadzenColumn Size="10">
|
||||
<RadzenFormField Text="@SearchFieldLabel" Style="width: 100%;">
|
||||
<RadzenAutoComplete @bind-Value="SearchText" Data="@SearchResults" TextProperty="@AutocompleteTextProperty"
|
||||
LoadData="@OnSearchAsync" MinLength="3" Placeholder="@SearchPlaceholder"
|
||||
Style="width: 100%;" Change="@OnItemSelected" />
|
||||
</RadzenFormField>
|
||||
</RadzenColumn>
|
||||
<RadzenColumn Size="2">
|
||||
<RadzenButton Text="Add" Icon="add" ButtonStyle="ButtonStyle.Primary" Click="@AddItemAsync"
|
||||
Disabled="@(SelectedItem == null)" Style="margin-top: 24px;" />
|
||||
</RadzenColumn>
|
||||
</RadzenRow>
|
||||
<div class="filter-input-row rz-mb-3">
|
||||
<div class="filter-input-col">
|
||||
<label class="field-label">@SearchFieldLabel</label>
|
||||
<RadzenDropDown @bind-Value="SelectedValue" Data="@SearchResults" TextProperty="@AutocompleteTextProperty"
|
||||
LoadData="@OnSearchAsync" FilterCaseSensitivity="FilterCaseSensitivity.CaseInsensitive"
|
||||
AllowFiltering="true" AllowClear="true"
|
||||
Placeholder="@SearchPlaceholder" Style="width: 100%;" />
|
||||
</div>
|
||||
<RadzenButton Text="Add" Icon="add" ButtonStyle="ButtonStyle.Primary" Click="@AddItemAsync"
|
||||
Disabled="@(SelectedValue == null)" />
|
||||
</div>
|
||||
}
|
||||
|
||||
<RadzenDataGrid @ref="Grid" Data="@Items" TItem="OperatorViewModel" AllowSorting="true" Style="min-height: 150px; max-height: 300px;">
|
||||
@@ -48,14 +46,27 @@
|
||||
</RadzenCard>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public List<OperatorViewModel> Operators
|
||||
{
|
||||
get => Items;
|
||||
set => Items = value;
|
||||
}
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<List<OperatorViewModel>> OperatorsChanged
|
||||
{
|
||||
get => ItemsChanged;
|
||||
set => ItemsChanged = value;
|
||||
}
|
||||
|
||||
protected override string PanelTitle => "Filter by Operator";
|
||||
protected override string SearchPlaceholder => "Search operators (3+ chars)...";
|
||||
protected override string SearchPlaceholder => "Search operators (2+ chars)...";
|
||||
protected override string SearchFieldLabel => "Name";
|
||||
protected override string AutocompleteTextProperty => "FullName";
|
||||
protected override string ClearConfirmMessage => "Are you sure you want to clear all operators?";
|
||||
|
||||
protected override object GetItemKey(OperatorViewModel item) => item.UserId;
|
||||
protected override string GetDisplayText(OperatorViewModel item) => item.FullName;
|
||||
|
||||
protected override async Task<List<OperatorViewModel>> SearchApiAsync(string filter)
|
||||
{
|
||||
|
||||
@@ -14,19 +14,17 @@
|
||||
|
||||
@if (!IsReadOnly)
|
||||
{
|
||||
<RadzenRow Gap="0.5rem" class="rz-mb-3">
|
||||
<RadzenColumn Size="10">
|
||||
<RadzenFormField Text="@SearchFieldLabel" Style="width: 100%;">
|
||||
<RadzenAutoComplete @bind-Value="SearchText" Data="@SearchResults" TextProperty="@AutocompleteTextProperty"
|
||||
LoadData="@OnSearchAsync" MinLength="3" Placeholder="@SearchPlaceholder"
|
||||
Style="width: 100%;" Change="@OnItemSelected" />
|
||||
</RadzenFormField>
|
||||
</RadzenColumn>
|
||||
<RadzenColumn Size="2">
|
||||
<RadzenButton Text="Add" Icon="add" ButtonStyle="ButtonStyle.Primary" Click="@AddItemAsync"
|
||||
Disabled="@(SelectedItem == null)" Style="margin-top: 24px;" />
|
||||
</RadzenColumn>
|
||||
</RadzenRow>
|
||||
<div class="filter-input-row rz-mb-3">
|
||||
<div class="filter-input-col">
|
||||
<label class="field-label">@SearchFieldLabel</label>
|
||||
<RadzenDropDown @bind-Value="SelectedValue" Data="@SearchResults" TextProperty="@AutocompleteTextProperty"
|
||||
LoadData="@OnSearchAsync" FilterCaseSensitivity="FilterCaseSensitivity.CaseInsensitive"
|
||||
AllowFiltering="true" AllowClear="true"
|
||||
Placeholder="@SearchPlaceholder" Style="width: 100%;" />
|
||||
</div>
|
||||
<RadzenButton Text="Add" Icon="add" ButtonStyle="ButtonStyle.Primary" Click="@AddItemAsync"
|
||||
Disabled="@(SelectedValue == null)" />
|
||||
</div>
|
||||
}
|
||||
|
||||
<RadzenDataGrid @ref="Grid" Data="@Items" TItem="ProfitCenterViewModel" AllowSorting="true" Style="min-height: 150px; max-height: 300px;">
|
||||
@@ -46,14 +44,27 @@
|
||||
</RadzenCard>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public List<ProfitCenterViewModel> ProfitCenters
|
||||
{
|
||||
get => Items;
|
||||
set => Items = value;
|
||||
}
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<List<ProfitCenterViewModel>> ProfitCentersChanged
|
||||
{
|
||||
get => ItemsChanged;
|
||||
set => ItemsChanged = value;
|
||||
}
|
||||
|
||||
protected override string PanelTitle => "Filter by Profit Center";
|
||||
protected override string SearchPlaceholder => "Search profit centers (3+ chars)...";
|
||||
protected override string SearchPlaceholder => "Search profit centers (2+ chars)...";
|
||||
protected override string SearchFieldLabel => "Profit Center";
|
||||
protected override string AutocompleteTextProperty => "Code";
|
||||
protected override string ClearConfirmMessage => "Are you sure you want to clear all profit centers?";
|
||||
|
||||
protected override object GetItemKey(ProfitCenterViewModel item) => item.Code;
|
||||
protected override string GetDisplayText(ProfitCenterViewModel item) => item.Code;
|
||||
|
||||
protected override async Task<List<ProfitCenterViewModel>> SearchApiAsync(string filter)
|
||||
{
|
||||
|
||||
@@ -14,19 +14,17 @@
|
||||
|
||||
@if (!IsReadOnly)
|
||||
{
|
||||
<RadzenRow Gap="0.5rem" class="rz-mb-3">
|
||||
<RadzenColumn Size="10">
|
||||
<RadzenFormField Text="@SearchFieldLabel" Style="width: 100%;">
|
||||
<RadzenAutoComplete @bind-Value="SearchText" Data="@SearchResults" TextProperty="@AutocompleteTextProperty"
|
||||
LoadData="@OnSearchAsync" MinLength="3" Placeholder="@SearchPlaceholder"
|
||||
Style="width: 100%;" Change="@OnItemSelected" />
|
||||
</RadzenFormField>
|
||||
</RadzenColumn>
|
||||
<RadzenColumn Size="2">
|
||||
<RadzenButton Text="Add" Icon="add" ButtonStyle="ButtonStyle.Primary" Click="@AddItemAsync"
|
||||
Disabled="@(SelectedItem == null)" Style="margin-top: 24px;" />
|
||||
</RadzenColumn>
|
||||
</RadzenRow>
|
||||
<div class="filter-input-row rz-mb-3">
|
||||
<div class="filter-input-col">
|
||||
<label class="field-label">@SearchFieldLabel</label>
|
||||
<RadzenDropDown @bind-Value="SelectedValue" Data="@SearchResults" TextProperty="@AutocompleteTextProperty"
|
||||
LoadData="@OnSearchAsync" FilterCaseSensitivity="FilterCaseSensitivity.CaseInsensitive"
|
||||
AllowFiltering="true" AllowClear="true"
|
||||
Placeholder="@SearchPlaceholder" Style="width: 100%;" />
|
||||
</div>
|
||||
<RadzenButton Text="Add" Icon="add" ButtonStyle="ButtonStyle.Primary" Click="@AddItemAsync"
|
||||
Disabled="@(SelectedValue == null)" />
|
||||
</div>
|
||||
}
|
||||
|
||||
<RadzenDataGrid @ref="Grid" Data="@Items" TItem="WorkCenterViewModel" AllowSorting="true" Style="min-height: 150px; max-height: 300px;">
|
||||
@@ -46,14 +44,27 @@
|
||||
</RadzenCard>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public List<WorkCenterViewModel> WorkCenters
|
||||
{
|
||||
get => Items;
|
||||
set => Items = value;
|
||||
}
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<List<WorkCenterViewModel>> WorkCentersChanged
|
||||
{
|
||||
get => ItemsChanged;
|
||||
set => ItemsChanged = value;
|
||||
}
|
||||
|
||||
protected override string PanelTitle => "Filter by Work Center";
|
||||
protected override string SearchPlaceholder => "Search work centers (3+ chars)...";
|
||||
protected override string SearchPlaceholder => "Search work centers (2+ chars)...";
|
||||
protected override string SearchFieldLabel => "Work Center";
|
||||
protected override string AutocompleteTextProperty => "Code";
|
||||
protected override string ClearConfirmMessage => "Are you sure you want to clear all work centers?";
|
||||
|
||||
protected override object GetItemKey(WorkCenterViewModel item) => item.Code;
|
||||
protected override string GetDisplayText(WorkCenterViewModel item) => item.Code;
|
||||
|
||||
protected override async Task<List<WorkCenterViewModel>> SearchApiAsync(string filter)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
@*
|
||||
RefreshStatusDetailDialog.razor - Per-table detail popup for a sync run.
|
||||
|
||||
Displays a grid of individual table sync results (table name, record count, pass/fail).
|
||||
*@
|
||||
|
||||
<RadzenText TextStyle="TextStyle.Subtitle1" class="rz-mb-2">
|
||||
@SyncRun.StartDt.ToString("MM/dd/yyyy hh:mm tt") — @(SyncRun.EndDt?.ToString("hh:mm tt") ?? "In Progress")
|
||||
</RadzenText>
|
||||
|
||||
<RadzenDataGrid Data="@SyncRun.Items" TItem="DataUpdateItemDto" AllowSorting="true" Style="text-align: center;">
|
||||
<Columns>
|
||||
<RadzenDataGridColumn TItem="DataUpdateItemDto" Property="TableName" Title="Table" Width="200px" TextAlign="TextAlign.Left" SortOrder="SortOrder.Ascending" />
|
||||
<RadzenDataGridColumn TItem="DataUpdateItemDto" Property="NumberRecords" Title="Records" Width="120px" TextAlign="TextAlign.Center" />
|
||||
<RadzenDataGridColumn TItem="DataUpdateItemDto" Property="WasSuccessful" Title="Status" Width="100px" TextAlign="TextAlign.Center">
|
||||
<Template Context="item">
|
||||
@if (item.WasSuccessful)
|
||||
{
|
||||
<RadzenBadge BadgeStyle="BadgeStyle.Success" Text="PASS" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<RadzenBadge BadgeStyle="BadgeStyle.Danger" Text="FAIL" />
|
||||
}
|
||||
</Template>
|
||||
</RadzenDataGridColumn>
|
||||
</Columns>
|
||||
</RadzenDataGrid>
|
||||
|
||||
<div class="rz-mt-4" style="text-align: right;">
|
||||
<RadzenButton Text="Close" ButtonStyle="ButtonStyle.Light" Click="@(() => DialogService.Close())" />
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter] public DataUpdateDto SyncRun { get; set; } = default!;
|
||||
|
||||
[Inject] private DialogService DialogService { get; set; } = default!;
|
||||
}
|
||||
@@ -7,61 +7,55 @@
|
||||
@namespace JdeScoping.Client.Components.Search
|
||||
|
||||
<RadzenCard class="rz-mb-4">
|
||||
<RadzenText TextStyle="TextStyle.H6" class="rz-mb-3">Search Details</RadzenText>
|
||||
<div class="search-details-header">
|
||||
<strong>Search Details</strong>
|
||||
</div>
|
||||
|
||||
<RadzenRow Gap="1rem">
|
||||
<RadzenColumn Size="12">
|
||||
<RadzenFormField Text="Search Type" Style="width: 100%;">
|
||||
<RadzenDropDown @bind-Value="SelectedSearchType" Data="@ValidCombinations" TextProperty="Name" ValueProperty="Id"
|
||||
Placeholder="Select type" Disabled="@Search.IsReadOnly" Change="@OnSearchTypeChangedHandler" Style="width: 100%;" />
|
||||
</RadzenFormField>
|
||||
<label class="field-label">Search Type</label>
|
||||
<RadzenDropDown @bind-Value="SelectedSearchType" Data="@ValidCombinations" TextProperty="Name" ValueProperty="Id"
|
||||
Placeholder="Select type" Disabled="@Search.IsReadOnly" Change="@OnSearchTypeChangedHandler" Style="width: 100%;" />
|
||||
</RadzenColumn>
|
||||
</RadzenRow>
|
||||
|
||||
<RadzenRow Gap="1rem" class="rz-mt-3">
|
||||
<RadzenColumn Size="12">
|
||||
<RadzenFormField Text="Name" Style="width: 100%;">
|
||||
<RadzenTextBox @bind-Value="Search.Name" Disabled="@Search.IsReadOnly" Style="width: 100%;" />
|
||||
</RadzenFormField>
|
||||
<label class="field-label">Name</label>
|
||||
<RadzenTextBox @bind-Value="Search.Name" Disabled="@Search.IsReadOnly" Style="width: 100%;" />
|
||||
<ValidationMessage For="@(() => Search.Name)" class="validation-message text-danger" />
|
||||
</RadzenColumn>
|
||||
</RadzenRow>
|
||||
|
||||
<RadzenRow Gap="1rem" class="rz-mt-3">
|
||||
<RadzenColumn Size="4">
|
||||
<RadzenFormField Text="Submitted At" Style="width: 100%;">
|
||||
<RadzenTextBox Value="@(Search.SubmitDt?.ToString("MM/dd/yyyy hh:mm:ss tt") ?? "")" ReadOnly="true" Style="width: 100%;" />
|
||||
</RadzenFormField>
|
||||
<label class="field-label">Submitted At</label>
|
||||
<RadzenTextBox Value="@(Search.SubmitDt?.ToString("MM/dd/yyyy hh:mm:ss tt") ?? "")" ReadOnly="true" class="readonly-input" Style="width: 100%;" />
|
||||
</RadzenColumn>
|
||||
<RadzenColumn Size="4">
|
||||
<RadzenFormField Text="Started At" Style="width: 100%;">
|
||||
<RadzenTextBox Value="@(Search.StartDt?.ToString("MM/dd/yyyy hh:mm:ss tt") ?? "")" ReadOnly="true" Style="width: 100%;" />
|
||||
</RadzenFormField>
|
||||
<label class="field-label">Started At</label>
|
||||
<RadzenTextBox Value="@(Search.StartDt?.ToString("MM/dd/yyyy hh:mm:ss tt") ?? "")" ReadOnly="true" class="readonly-input" Style="width: 100%;" />
|
||||
</RadzenColumn>
|
||||
<RadzenColumn Size="4">
|
||||
<RadzenFormField Text="Completed At" Style="width: 100%;">
|
||||
<RadzenTextBox Value="@(Search.EndDt?.ToString("MM/dd/yyyy hh:mm:ss tt") ?? "")" ReadOnly="true" Style="width: 100%;" />
|
||||
</RadzenFormField>
|
||||
<label class="field-label">Completed At</label>
|
||||
<RadzenTextBox Value="@(Search.EndDt?.ToString("MM/dd/yyyy hh:mm:ss tt") ?? "")" ReadOnly="true" class="readonly-input" Style="width: 100%;" />
|
||||
</RadzenColumn>
|
||||
</RadzenRow>
|
||||
|
||||
<RadzenRow Gap="1rem" class="rz-mt-3">
|
||||
<RadzenColumn Size="4">
|
||||
<RadzenFormField Text="User" Style="width: 100%;">
|
||||
<RadzenTextBox Value="@Search.UserName" ReadOnly="true" Style="width: 100%;" />
|
||||
</RadzenFormField>
|
||||
<label class="field-label">User</label>
|
||||
<RadzenTextBox Value="@Search.UserName" ReadOnly="true" class="readonly-input" Style="width: 100%;" />
|
||||
</RadzenColumn>
|
||||
<RadzenColumn Size="4">
|
||||
<RadzenFormField Text="Status" Style="width: 100%;">
|
||||
<RadzenTextBox Value="@Search.Status" ReadOnly="true" Style="@($"width: 100%; background-color: {Search.StatusColor};")" />
|
||||
</RadzenFormField>
|
||||
<label class="field-label">Status</label>
|
||||
<RadzenTextBox Value="@Search.Status" ReadOnly="true" Style="@($"width: 100%; background-color: {Search.StatusColor};")" />
|
||||
</RadzenColumn>
|
||||
<RadzenColumn Size="4">
|
||||
@if (Search.HasResults)
|
||||
{
|
||||
<RadzenFormField Text=" " Style="width: 100%;">
|
||||
<RadzenButton Text="Download Results" Icon="download" ButtonStyle="ButtonStyle.Success" Click="@OnDownloadResults" Style="width: 100%;" />
|
||||
</RadzenFormField>
|
||||
<label class="field-label"> </label>
|
||||
<RadzenButton Text="Download Results" Icon="download" ButtonStyle="ButtonStyle.Success" Click="@OnDownloadResults" Style="width: 100%;" />
|
||||
}
|
||||
</RadzenColumn>
|
||||
</RadzenRow>
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.1" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="10.0.1" />
|
||||
<PackageReference Include="Radzen.Blazor" Version="8.4.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" />
|
||||
<PackageReference Include="Radzen.Blazor" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -9,11 +9,15 @@
|
||||
<div class="navbar-left">
|
||||
<a href="/" class="navbar-brand">JDE Scoping Tool</a>
|
||||
<nav class="navbar-nav">
|
||||
<NavLink class="nav-link" href="/" Match="NavLinkMatch.All">Searches</NavLink>
|
||||
<NavLink class="nav-link" href="/search">New Search</NavLink>
|
||||
<NavLink class="nav-link" href="/search/queue">Search Queue</NavLink>
|
||||
<NavLink class="nav-link" href="/refresh-status">Refresh Status</NavLink>
|
||||
<NavLink class="nav-link" href="/data-sync/requests">Data Sync</NavLink>
|
||||
<AuthorizeView>
|
||||
<Authorized>
|
||||
<NavLink class="nav-link" href="/" Match="NavLinkMatch.All">Searches</NavLink>
|
||||
<NavLink class="nav-link" href="/search">New Search</NavLink>
|
||||
<NavLink class="nav-link" href="/search/queue">Search Queue</NavLink>
|
||||
<NavLink class="nav-link" href="/refresh-status">Refresh Status</NavLink>
|
||||
<NavLink class="nav-link" href="/data-sync/requests">Data Sync</NavLink>
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="navbar-right">
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
RefreshStatus.razor - Data cache refresh status dashboard.
|
||||
|
||||
Shows the status of JDE/CMS data synchronization jobs (hourly, daily, mass).
|
||||
Allows filtering by date range and entity name.
|
||||
Allows filtering by date range. Click a row to see per-table detail.
|
||||
*@
|
||||
@page "/refresh-status"
|
||||
@attribute [Authorize]
|
||||
@inject IRefreshStatusService RefreshStatusService
|
||||
@inject DialogService DialogService
|
||||
|
||||
<PageTitle>Cache Refresh Status - JDE Scoping Tool</PageTitle>
|
||||
|
||||
@@ -38,38 +39,30 @@
|
||||
else
|
||||
{
|
||||
<RadzenDataGrid Data="@_results" TItem="DataUpdateDto" AllowSorting="true" AllowPaging="true" PageSize="20"
|
||||
PagerHorizontalAlign="HorizontalAlign.Center" AllowColumnResize="true" Style="text-align: center;">
|
||||
PagerHorizontalAlign="HorizontalAlign.Center" AllowColumnResize="true" Style="text-align: center;"
|
||||
SelectionMode="DataGridSelectionMode.Single" RowSelect="@OnRowSelect">
|
||||
<Columns>
|
||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="StartDT" Title="Start" Width="160px">
|
||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="StartDt" Title="Start" Width="180px">
|
||||
<Template Context="item">
|
||||
@item.StartDt.ToString("MM/dd/yyyy hh:mm tt")
|
||||
</Template>
|
||||
</RadzenDataGridColumn>
|
||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="EndDT" Title="End" Width="160px">
|
||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="EndDt" Title="End" Width="180px">
|
||||
<Template Context="item">
|
||||
@(item.EndDt?.ToString("MM/dd/yyyy hh:mm tt") ?? "")
|
||||
</Template>
|
||||
</RadzenDataGridColumn>
|
||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="BranchRecords" Title="Branch" Width="80px" TextAlign="TextAlign.Center" />
|
||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="ProfitCenterRecords" Title="Profit Center" Width="100px" TextAlign="TextAlign.Center" />
|
||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="WorkCenterRecords" Title="Work Center" Width="100px" TextAlign="TextAlign.Center" />
|
||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="OrgHierarchyRecords" Title="Org Hierarchy" Width="100px" TextAlign="TextAlign.Center" />
|
||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="StatusCodeRecords" Title="Status Code" Width="100px" TextAlign="TextAlign.Center" />
|
||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="UserRecords" Title="User" Width="80px" TextAlign="TextAlign.Center" />
|
||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="ItemRecords" Title="Item" Width="80px" TextAlign="TextAlign.Center" />
|
||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="LotRecords" Title="Lot" Width="80px" TextAlign="TextAlign.Center" />
|
||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="WorkOrderRecords" Title="Work Order" Width="100px" TextAlign="TextAlign.Center" />
|
||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="WorkOrderStepRecords" Title="WO Step" Width="90px" TextAlign="TextAlign.Center" />
|
||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="WorkOrderComponentRecords" Title="WO Component" Width="110px" TextAlign="TextAlign.Center" />
|
||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="WasSuccessful" Title="Was Successful?" Width="120px" TextAlign="TextAlign.Center">
|
||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="PassedCount" Title="Passed" Width="100px" TextAlign="TextAlign.Center" />
|
||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="FailedCount" Title="Failed" Width="100px" TextAlign="TextAlign.Center" />
|
||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="WasSuccessful" Title="Status" Width="120px" TextAlign="TextAlign.Center">
|
||||
<Template Context="item">
|
||||
@if (item.WasSuccessful)
|
||||
{
|
||||
<RadzenBadge BadgeStyle="BadgeStyle.Success" Text="YES" />
|
||||
<RadzenBadge BadgeStyle="BadgeStyle.Success" Text="PASS" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<RadzenBadge BadgeStyle="BadgeStyle.Danger" Text="NO" />
|
||||
<RadzenBadge BadgeStyle="BadgeStyle.Danger" Text="FAIL" />
|
||||
}
|
||||
</Template>
|
||||
</RadzenDataGridColumn>
|
||||
@@ -96,7 +89,6 @@ else
|
||||
try
|
||||
{
|
||||
_results = await RefreshStatusService.GetRefreshStatusAsync(_minDt, _maxDt);
|
||||
// Sort by StartDT descending
|
||||
_results = _results.OrderByDescending(r => r.StartDt).ToList();
|
||||
}
|
||||
finally
|
||||
@@ -104,4 +96,12 @@ else
|
||||
_isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnRowSelect(DataUpdateDto row)
|
||||
{
|
||||
await DialogService.OpenAsync<JdeScoping.Client.Components.RefreshStatus.RefreshStatusDetailDialog>(
|
||||
"Sync Run Detail",
|
||||
new Dictionary<string, object> { { "SyncRun", row } },
|
||||
new DialogOptions { Width = "600px", Resizable = true, Draggable = true });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<RadzenText TextStyle="TextStyle.H4" class="rz-m-0">Search</RadzenText>
|
||||
@if (!_search.IsReadOnly)
|
||||
{
|
||||
<RadzenButton Text="Submit" Icon="send" ButtonStyle="ButtonStyle.Primary" Click="@SubmitSearchAsync" IsBusy="@_isSubmitting" BusyText="Submitting..." />
|
||||
<RadzenButton Text="Submit" Icon="send" ButtonStyle="ButtonStyle.Primary" Click="@SubmitSearchAsync" IsBusy="@_isSubmitting" BusyText="Submitting..." class="btn-submit-blue" />
|
||||
}
|
||||
</RadzenStack>
|
||||
|
||||
|
||||
@@ -232,10 +232,20 @@ code {
|
||||
|
||||
.navbar-nav {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
/* Ensure top-header nav stays horizontal when host serves Blazor assets. */
|
||||
.navbar-fixed-top .navbar-left nav.navbar-nav {
|
||||
display: inline-flex !important;
|
||||
flex-direction: row !important;
|
||||
flex-wrap: nowrap !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: #9d9d9d;
|
||||
text-decoration: none;
|
||||
@@ -286,6 +296,53 @@ code {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Legacy-style form labels - bold, above the field */
|
||||
.field-label {
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.25rem;
|
||||
font-size: 0.95rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* Read-only input styling - grey background */
|
||||
.readonly-input {
|
||||
background-color: #eee !important;
|
||||
color: #555 !important;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Search details card header bar */
|
||||
.search-details-header {
|
||||
background-color: #f5f5f5;
|
||||
padding: 0.75rem 1rem;
|
||||
border-bottom: 1px solid #ddd;
|
||||
margin: -1.25rem -1.25rem 1rem -1.25rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* Blue submit button (legacy style) */
|
||||
.btn-submit-blue {
|
||||
background-color: #337ab7 !important;
|
||||
border-color: #2e6da4 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.btn-submit-blue:hover {
|
||||
background-color: #286090 !important;
|
||||
border-color: #204d74 !important;
|
||||
}
|
||||
|
||||
/* Filter panel input row - align Add button with input bottom */
|
||||
.filter-input-row {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.filter-input-row .filter-input-col {
|
||||
flex: 0 1 41.67%;
|
||||
}
|
||||
|
||||
/* RadzenUpload inline button style (no drop zone) */
|
||||
.rz-upload-inline .rz-fileupload-buttonbar {
|
||||
padding: 0;
|
||||
@@ -294,4 +351,4 @@ code {
|
||||
}
|
||||
.rz-upload-inline .rz-fileupload-content {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,4 +13,13 @@ public partial interface ILotFinderRepository
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Latest data updates.</returns>
|
||||
Task<List<DataUpdate>> GetLastDataUpdatesAsync(CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all data update records within a date range.
|
||||
/// </summary>
|
||||
/// <param name="minDt">Start of range (inclusive).</param>
|
||||
/// <param name="maxDt">End of range (inclusive, end of day).</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Data updates within the range.</returns>
|
||||
Task<List<DataUpdate>> GetDataUpdatesInRangeAsync(DateTime minDt, DateTime maxDt, CancellationToken ct = default);
|
||||
}
|
||||
|
||||
@@ -7,15 +7,15 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Cronos" Version="0.11.1" />
|
||||
<PackageReference Include="OneOf" Version="3.0.271" />
|
||||
<PackageReference Include="OneOf.SourceGenerator" Version="3.0.271" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.1" />
|
||||
<PackageReference Include="Cronos" />
|
||||
<PackageReference Include="OneOf" />
|
||||
<PackageReference Include="OneOf.SourceGenerator" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -2,38 +2,25 @@ namespace JdeScoping.Core.Models.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// DTO for data refresh/sync status display.
|
||||
/// Aggregates record counts from multiple table updates into a single row.
|
||||
/// Summarises a single sync run with pass/fail counts and per-table detail.
|
||||
/// </summary>
|
||||
public class DataUpdateDto
|
||||
{
|
||||
/// <summary>The start time of the data update.</summary>
|
||||
public DateTime StartDt { get; set; }
|
||||
|
||||
/// <summary>The end time of the data update.</summary>
|
||||
public DateTime? EndDt { get; set; }
|
||||
|
||||
/// <summary>The number of branch records updated.</summary>
|
||||
public int BranchRecords { get; set; }
|
||||
/// <summary>The number of profit center records updated.</summary>
|
||||
public int ProfitCenterRecords { get; set; }
|
||||
/// <summary>The number of work center records updated.</summary>
|
||||
public int WorkCenterRecords { get; set; }
|
||||
/// <summary>The number of organizational hierarchy records updated.</summary>
|
||||
public int OrgHierarchyRecords { get; set; }
|
||||
/// <summary>The number of status code records updated.</summary>
|
||||
public int StatusCodeRecords { get; set; }
|
||||
/// <summary>The number of user records updated.</summary>
|
||||
public int UserRecords { get; set; }
|
||||
/// <summary>The number of item records updated.</summary>
|
||||
public int ItemRecords { get; set; }
|
||||
/// <summary>The number of lot records updated.</summary>
|
||||
public int LotRecords { get; set; }
|
||||
/// <summary>The number of work order records updated.</summary>
|
||||
public int WorkOrderRecords { get; set; }
|
||||
/// <summary>The number of work order step records updated.</summary>
|
||||
public int WorkOrderStepRecords { get; set; }
|
||||
/// <summary>The number of work order component records updated.</summary>
|
||||
public int WorkOrderComponentRecords { get; set; }
|
||||
|
||||
/// <summary>Whether the data update was successful.</summary>
|
||||
/// <summary>Whether the data update was successful overall.</summary>
|
||||
public bool WasSuccessful { get; set; }
|
||||
|
||||
/// <summary>Number of table syncs that succeeded.</summary>
|
||||
public int PassedCount { get; set; }
|
||||
|
||||
/// <summary>Number of table syncs that failed.</summary>
|
||||
public int FailedCount { get; set; }
|
||||
|
||||
/// <summary>Per-table detail for this sync run.</summary>
|
||||
public List<DataUpdateItemDto> Items { get; set; } = [];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace JdeScoping.Core.Models.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// Per-table detail within a data sync run.
|
||||
/// </summary>
|
||||
public class DataUpdateItemDto
|
||||
{
|
||||
/// <summary>The cache table that was synced.</summary>
|
||||
public string TableName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Whether this table sync succeeded.</summary>
|
||||
public bool WasSuccessful { get; set; }
|
||||
|
||||
/// <summary>Number of records synced for this table.</summary>
|
||||
public long NumberRecords { get; set; }
|
||||
}
|
||||
@@ -7,17 +7,17 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" Version="2.1.66" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.3" />
|
||||
<PackageReference Include="Oracle.ManagedDataAccess.Core" Version="23.26.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="10.0.1" />
|
||||
<PackageReference Include="SqlKata" Version="3.2.3" />
|
||||
<PackageReference Include="SqlKata.Execution" Version="3.2.3" />
|
||||
<PackageReference Include="Dapper" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" />
|
||||
<PackageReference Include="Oracle.ManagedDataAccess.Core" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" />
|
||||
<PackageReference Include="SqlKata" />
|
||||
<PackageReference Include="SqlKata.Execution" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -24,4 +24,21 @@ public static partial class LotFinderQueries
|
||||
cte.NumberRecords
|
||||
FROM DU_CTE cte
|
||||
WHERE cte.RN = 1";
|
||||
|
||||
/// <summary>
|
||||
/// Gets all data update records within a date range, ordered by StartDT descending.
|
||||
/// </summary>
|
||||
public const string SqlGetDataUpdatesInRange = @"
|
||||
SELECT du.SourceSystem,
|
||||
du.SourceData,
|
||||
du.TableName,
|
||||
du.StartDT,
|
||||
du.EndDT,
|
||||
du.UpdateType,
|
||||
du.WasSuccessful,
|
||||
du.NumberRecords
|
||||
FROM dbo.DataUpdate AS du
|
||||
WHERE du.StartDT >= @minDt
|
||||
AND du.StartDT <= @maxDt
|
||||
ORDER BY du.StartDT DESC";
|
||||
}
|
||||
|
||||
@@ -20,4 +20,17 @@ public partial class LotFinderRepository
|
||||
commandTimeout: _options.Value.DefaultTimeoutSeconds)).ToList(),
|
||||
ct);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<DataUpdate>> GetDataUpdatesInRangeAsync(DateTime minDt, DateTime maxDt, CancellationToken ct = default)
|
||||
{
|
||||
return await ExecuteQueryAsync(
|
||||
nameof(GetDataUpdatesInRangeAsync),
|
||||
"SQL_GET_DATA_UPDATES_IN_RANGE",
|
||||
async connection => (await connection.QueryAsync<DataUpdate>(
|
||||
LotFinderQueries.SqlGetDataUpdatesInRange,
|
||||
new { minDt, maxDt },
|
||||
commandTimeout: _options.Value.DefaultTimeoutSeconds)).ToList(),
|
||||
ct);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="protobuf-net-data" Version="4.1.0" />
|
||||
<PackageReference Include="protobuf-net-data" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -17,14 +17,14 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.DataAnnotations" Version="10.0.1" />
|
||||
<PackageReference Include="ZstdSharp.Port" Version="0.8.1" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.DataAnnotations" />
|
||||
<PackageReference Include="ZstdSharp.Port" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="dbup-sqlserver" Version="6.0.16" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.1" />
|
||||
<PackageReference Include="dbup-sqlserver" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NPOI" Version="2.7.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.1" />
|
||||
<PackageReference Include="NPOI" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -12,12 +12,12 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="10.0.1" />
|
||||
<PackageReference Include="Serilog" Version="4.*" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="8.*" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.*" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.*" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" />
|
||||
<PackageReference Include="Serilog" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" />
|
||||
<PackageReference Include="Serilog.Sinks.File" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -62,7 +62,7 @@ try
|
||||
.AddDataAccess(builder.Configuration) // 1. Database access + search processing
|
||||
.AddInfrastructure(builder.Configuration) // 2. Infrastructure (JDE/CMS/Auth)
|
||||
.AddDataSyncServices(builder.Configuration) // 3. Data sync background service
|
||||
.AddExcelIO(builder.Configuration) // 4. Result export
|
||||
.AddExcelIO(builder.Configuration) // 4. Result export
|
||||
.AddWebApi(builder.Configuration); // 5. Web API (controllers, auth, SignalR)
|
||||
|
||||
var app = builder.Build();
|
||||
@@ -90,7 +90,6 @@ try
|
||||
}
|
||||
|
||||
app.UseStaticFiles();
|
||||
app.UseBlazorFrameworkFiles();
|
||||
app.UseRouting();
|
||||
|
||||
// Configure Web API middleware (authentication, authorization, controllers, SignalR hub)
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.1" />
|
||||
<PackageReference Include="SecureStore" Version="1.2.0" />
|
||||
<PackageReference Include="System.DirectoryServices.Protocols" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" />
|
||||
<PackageReference Include="SecureStore" />
|
||||
<PackageReference Include="System.DirectoryServices.Protocols" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.*" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.*" />
|
||||
<PackageReference Include="Serilog" Version="4.*" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="8.*" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.*" />
|
||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" />
|
||||
<PackageReference Include="Serilog" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" />
|
||||
<PackageReference Include="System.CommandLine" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DiffPlex" Version="1.7.*" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.*" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.*" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.*" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.*" />
|
||||
<PackageReference Include="SecureStore" Version="1.2.0" />
|
||||
<PackageReference Include="DiffPlex" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" />
|
||||
<PackageReference Include="SecureStore" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -12,18 +12,18 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="11.2.*" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.2.*" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.*" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.2.*" />
|
||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.*" />
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="11.2.*" Condition="'$(Configuration)' == 'Debug'" />
|
||||
<PackageReference Include="MessageBox.Avalonia" Version="3.1.*" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.*" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.*" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.*" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.*" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.*" />
|
||||
<PackageReference Include="Avalonia" />
|
||||
<PackageReference Include="Avalonia.Desktop" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" />
|
||||
<PackageReference Include="Avalonia.Fonts.Inter" />
|
||||
<PackageReference Include="Avalonia.Diagnostics" Condition="'$(Configuration)' == 'Debug'" />
|
||||
<PackageReference Include="MessageBox.Avalonia" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" />
|
||||
<PackageReference Include="Serilog.Sinks.File" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<AssemblyName>jdescoping-devloader</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" />
|
||||
<PackageReference Include="Serilog" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\JdeScoping.DataSync.Dev\JdeScoping.DataSync.Dev.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,88 @@
|
||||
using System.Diagnostics;
|
||||
using JdeScoping.DataSync.Dev;
|
||||
using JdeScoping.DataSync.Dev.Options;
|
||||
using JdeScoping.DataSync.Dev.Services;
|
||||
using JdeScoping.DevLoader;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Serilog;
|
||||
|
||||
const string DefaultConnectionString =
|
||||
"Server=localhost,1434;Database=ScopingTool;User Id=scopingapp;Password=Sc0ping@pp_Dev#2024;TrustServerCertificate=true;Encrypt=false";
|
||||
|
||||
// --- Parse arguments ---
|
||||
string? cacheDir = null;
|
||||
string connectionString = DefaultConnectionString;
|
||||
|
||||
for (var i = 0; i < args.Length; i++)
|
||||
{
|
||||
switch (args[i])
|
||||
{
|
||||
case "--cache-dir" when i + 1 < args.Length:
|
||||
cacheDir = args[++i];
|
||||
break;
|
||||
case "--connection-string" when i + 1 < args.Length:
|
||||
connectionString = args[++i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(cacheDir))
|
||||
{
|
||||
Console.Error.WriteLine("Usage: jdescoping-devloader --cache-dir <path> [--connection-string <cs>]");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// --- Set up Serilog ---
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Information()
|
||||
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}")
|
||||
.CreateLogger();
|
||||
|
||||
using var loggerFactory = LoggerFactory.Create(builder =>
|
||||
builder.AddSerilog(Log.Logger, dispose: false));
|
||||
|
||||
var logger = loggerFactory.CreateLogger<DevEtlRegistry>();
|
||||
var pipelineLogger = loggerFactory.CreateLogger<JdeScoping.DataSync.Etl.Pipeline.EtlPipeline>();
|
||||
|
||||
// --- Build components ---
|
||||
var connectionFactory = new SimpleDbConnectionFactory(connectionString);
|
||||
var options = Options.Create(new DevPipelineOptions());
|
||||
var pipelineFactory = new DevEtlPipelineFactory(connectionFactory, options, pipelineLogger);
|
||||
var registry = new DevEtlRegistry(pipelineFactory, cacheDir, logger);
|
||||
|
||||
// --- Run ---
|
||||
var tables = registry.GetAvailableTables().ToList();
|
||||
Log.Information("Found {Count} tables to load from {Dir}", tables.Count, cacheDir);
|
||||
Log.Information("Tables: {Tables}", string.Join(", ", tables));
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
var results = await registry.RunAllParallelAsync(maxDegreeOfParallelism: 4);
|
||||
sw.Stop();
|
||||
|
||||
// --- Report ---
|
||||
Log.Information("=== Dev ETL Complete ({Elapsed:g}) ===", sw.Elapsed);
|
||||
|
||||
var succeeded = 0;
|
||||
var failed = 0;
|
||||
long totalRows = 0;
|
||||
|
||||
foreach (var r in results.OrderBy(r => r.Success ? 0 : 1))
|
||||
{
|
||||
var tableName = r.Steps.FirstOrDefault()?.StepName?.Replace("_Dev", "") ?? "Unknown";
|
||||
if (r.Success)
|
||||
{
|
||||
succeeded++;
|
||||
totalRows += r.TotalRows;
|
||||
Log.Information(" OK {Table,-30} {Rows,10:N0} rows ({Elapsed:g})", tableName, r.TotalRows, r.Elapsed);
|
||||
}
|
||||
else
|
||||
{
|
||||
failed++;
|
||||
Log.Error(" FAIL {Table,-30} {Error}", tableName, r.Error?.Message);
|
||||
}
|
||||
}
|
||||
|
||||
Log.Information("Summary: {Succeeded} succeeded, {Failed} failed, {TotalRows:N0} total rows", succeeded, failed, totalRows);
|
||||
|
||||
return failed > 0 ? 1 : 0;
|
||||
@@ -0,0 +1,39 @@
|
||||
using JdeScoping.DataAccess.Interfaces;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Oracle.ManagedDataAccess.Client;
|
||||
|
||||
namespace JdeScoping.DevLoader;
|
||||
|
||||
/// <summary>
|
||||
/// Minimal connection factory for the dev loader.
|
||||
/// Only supports SQL Server (LotFinder cache); Oracle methods throw NotSupportedException.
|
||||
/// </summary>
|
||||
public class SimpleDbConnectionFactory : IDbConnectionFactory
|
||||
{
|
||||
private readonly string _sqlConnectionString;
|
||||
|
||||
public SimpleDbConnectionFactory(string sqlConnectionString)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(sqlConnectionString);
|
||||
_sqlConnectionString = sqlConnectionString;
|
||||
}
|
||||
|
||||
public async Task<SqlConnection> CreateLotFinderConnectionAsync(CancellationToken ct = default)
|
||||
{
|
||||
var conn = new SqlConnection(_sqlConnectionString);
|
||||
await conn.OpenAsync(ct);
|
||||
return conn;
|
||||
}
|
||||
|
||||
public Task<OracleConnection> CreateJdeConnectionAsync(CancellationToken ct = default)
|
||||
=> throw new NotSupportedException("Oracle JDE connections are not available in the dev loader.");
|
||||
|
||||
public Task<OracleConnection> CreateJdeStageConnectionAsync(CancellationToken ct = default)
|
||||
=> throw new NotSupportedException("Oracle JDE Stage connections are not available in the dev loader.");
|
||||
|
||||
public Task<OracleConnection> CreateCmsConnectionAsync(CancellationToken ct = default)
|
||||
=> throw new NotSupportedException("Oracle CMS connections are not available in the dev loader.");
|
||||
|
||||
public Task<OracleConnection> CreateGiwConnectionAsync(CancellationToken ct = default)
|
||||
=> throw new NotSupportedException("Oracle GIW connections are not available in the dev loader.");
|
||||
}
|
||||
@@ -16,14 +16,14 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="LdapForNet" Version="2.7.15" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="Shouldly" Version="4.2.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.1">
|
||||
<PackageReference Include="LdapForNet" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="NSubstitute" />
|
||||
<PackageReference Include="Shouldly" />
|
||||
<PackageReference Include="xunit" />
|
||||
<PackageReference Include="xunit.runner.visualstudio">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -111,11 +111,13 @@ public class ManualSyncControllerTests
|
||||
var okResult = (OkObjectResult)result.Result!;
|
||||
var viewModels = okResult.Value.ShouldBeAssignableTo<List<PipelineInfoViewModel>>()!;
|
||||
viewModels.Count.ShouldBe(2);
|
||||
viewModels[0].Name.ShouldBe("WorkOrders");
|
||||
viewModels[0].Name.ShouldBe("Items");
|
||||
viewModels[0].SupportedSyncTypes.ShouldContain("mass");
|
||||
viewModels[0].SupportedSyncTypes.ShouldContain("daily");
|
||||
viewModels[0].SupportedSyncTypes.ShouldContain("hourly");
|
||||
viewModels[1].Name.ShouldBe("Items");
|
||||
viewModels[1].Name.ShouldBe("WorkOrders");
|
||||
viewModels[1].SupportedSyncTypes.ShouldContain("mass");
|
||||
viewModels[1].SupportedSyncTypes.ShouldContain("daily");
|
||||
viewModels[1].SupportedSyncTypes.ShouldContain("hourly");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -13,12 +13,12 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="Shouldly" Version="4.2.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.1">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="NSubstitute" />
|
||||
<PackageReference Include="Shouldly" />
|
||||
<PackageReference Include="xunit" />
|
||||
<PackageReference Include="xunit.runner.visualstudio">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -12,13 +12,13 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="RichardSzalay.MockHttp" Version="7.0.0" />
|
||||
<PackageReference Include="Shouldly" Version="4.3.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
|
||||
<PackageReference Include="coverlet.collector" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="NSubstitute" />
|
||||
<PackageReference Include="RichardSzalay.MockHttp" />
|
||||
<PackageReference Include="Shouldly" />
|
||||
<PackageReference Include="xunit" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="Shouldly" Version="4.3.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
|
||||
<PackageReference Include="coverlet.collector" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="NSubstitute" />
|
||||
<PackageReference Include="Shouldly" />
|
||||
<PackageReference Include="xunit" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -9,17 +9,17 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="xunit" />
|
||||
<PackageReference Include="xunit.runner.visualstudio">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Shouldly" Version="4.3.0" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||
<PackageReference Include="Shouldly" />
|
||||
<PackageReference Include="NSubstitute" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" />
|
||||
<PackageReference Include="coverlet.collector">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -9,23 +9,23 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="xunit" />
|
||||
<PackageReference Include="xunit.runner.visualstudio">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Shouldly" Version="4.3.0" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||
<PackageReference Include="Shouldly" />
|
||||
<PackageReference Include="NSubstitute" />
|
||||
<PackageReference Include="coverlet.collector">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.3" />
|
||||
<PackageReference Include="Dapper" Version="2.1.66" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" />
|
||||
<PackageReference Include="Dapper" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -9,26 +9,26 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="xunit" />
|
||||
<PackageReference Include="xunit.runner.visualstudio">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Shouldly" Version="4.3.0" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||
<PackageReference Include="Shouldly" />
|
||||
<PackageReference Include="NSubstitute" />
|
||||
<PackageReference Include="coverlet.collector">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.3" />
|
||||
<PackageReference Include="Dapper" Version="2.1.66" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" />
|
||||
<PackageReference Include="Dapper" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="Dapper" Version="2.1.35" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="Shouldly" Version="4.3.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
|
||||
<PackageReference Include="coverlet.collector" />
|
||||
<PackageReference Include="Dapper" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="NSubstitute" />
|
||||
<PackageReference Include="Shouldly" />
|
||||
<PackageReference Include="xunit" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -9,16 +9,16 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="NPOI" Version="2.7.5" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="xunit" />
|
||||
<PackageReference Include="NPOI" />
|
||||
<PackageReference Include="xunit.runner.visualstudio">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Shouldly" Version="4.3.0" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||
<PackageReference Include="Shouldly" />
|
||||
<PackageReference Include="NSubstitute" />
|
||||
<PackageReference Include="coverlet.collector">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="Shouldly" Version="4.3.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
|
||||
<PackageReference Include="coverlet.collector" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="NSubstitute" />
|
||||
<PackageReference Include="Shouldly" />
|
||||
<PackageReference Include="xunit" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="Shouldly" Version="4.3.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
|
||||
<PackageReference Include="coverlet.collector" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="NSubstitute" />
|
||||
<PackageReference Include="Shouldly" />
|
||||
<PackageReference Include="xunit" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using JdeScoping.Core.Models.Auth;
|
||||
using JdeScoping.Ui.Tests.Support;
|
||||
|
||||
namespace JdeScoping.Ui.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// API-level smoke tests for the authentication endpoint against the Docker host.
|
||||
/// Validates the RSA public-key exchange, encrypted login, and session cookie flow.
|
||||
/// Requires a running Docker host (Category: RequiresDockerHost).
|
||||
/// </summary>
|
||||
public class AuthApiSmokeTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies the full login flow: fetch public key, encrypt credentials, POST login, and confirm session via /me.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Steps:
|
||||
/// 1. Create an HttpClient with a CookieContainer for session tracking.
|
||||
/// 2. GET /api/auth/public-key and verify the PEM response.
|
||||
/// 3. RSA-encrypt a test login payload using the returned public key.
|
||||
/// 4. POST /api/auth/login with the encrypted payload and assert HTTP 200.
|
||||
/// 5. GET /api/auth/me and assert HTTP 200 (session is authenticated).
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
[Trait("Category", "RequiresDockerHost")]
|
||||
public async Task AuthApi_Login_WorksAgainstDockerHost()
|
||||
{
|
||||
var cookies = new CookieContainer();
|
||||
using var handler = new HttpClientHandler { CookieContainer = cookies };
|
||||
using var client = new HttpClient(handler) { BaseAddress = new Uri(UiTestSettings.BaseUrl) };
|
||||
|
||||
var key = await client.GetFromJsonAsync<PublicKeyResponse>("api/auth/public-key");
|
||||
Assert.NotNull(key);
|
||||
Assert.Contains("BEGIN PUBLIC KEY", key!.PublicKeyPem);
|
||||
|
||||
string payload = JsonSerializer.Serialize(new LoginModel { Username = "testuser", Password = "testpass" });
|
||||
|
||||
using var rsa = RSA.Create();
|
||||
rsa.ImportFromPem(key.PublicKeyPem);
|
||||
byte[] encrypted = rsa.Encrypt(Encoding.UTF8.GetBytes(payload), RSAEncryptionPadding.OaepSHA256);
|
||||
|
||||
var login = await client.PostAsJsonAsync("api/auth/login",
|
||||
new EncryptedLoginRequest(Convert.ToBase64String(encrypted)));
|
||||
Assert.Equal(HttpStatusCode.OK, login.StatusCode);
|
||||
|
||||
var me = await client.GetAsync("api/auth/me");
|
||||
Assert.Equal(HttpStatusCode.OK, me.StatusCode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using JdeScoping.Ui.Tests.Support;
|
||||
|
||||
namespace JdeScoping.Ui.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Playwright UI tests for the "Component Lot" search type (TC-020).
|
||||
/// Validates search form interaction in smoke mode and full submission with workbook upload in strict mode.
|
||||
/// Requires a running Docker host (Category: RequiresDockerHost).
|
||||
/// </summary>
|
||||
public sealed class ComponentLotSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies the Component Lot search form submits with an uploaded workbook filter (TC-020).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Steps (smoke mode stops after step 4; strict mode runs all steps):
|
||||
/// 1. Navigate to the search page.
|
||||
/// 2. Enter the search name "MIGRATED-TC-020".
|
||||
/// 3. Select the "Component Lot" search type from the dropdown.
|
||||
/// 4. Verify the dropdown displays the selected type.
|
||||
/// 5. Upload "single_lot.xlsx" to the "Filter By Component Lot" panel (strict only).
|
||||
/// 6. Click Submit (strict only).
|
||||
/// 7. Assert no error notification is present (strict only).
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
[Trait("Category", "RequiresDockerHost")]
|
||||
public Task ComponentLot_SubmitsWithUploadedWorkbook()
|
||||
{
|
||||
return RunSearchSubmissionAsync(
|
||||
UiSearchTypes.ComponentLot,
|
||||
"MIGRATED-TC-020",
|
||||
uploads:
|
||||
[
|
||||
("Filter By Component Lot", "single_lot.xlsx")
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using JdeScoping.Ui.Tests.Helpers;
|
||||
using JdeScoping.Ui.Tests.Support;
|
||||
|
||||
namespace JdeScoping.Ui.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Playwright UI smoke tests for the Data Sync Requests page.
|
||||
/// Validates that the page loads and shows action buttons or redirects to search.
|
||||
/// Requires a running Docker host (Category: RequiresDockerHost).
|
||||
/// </summary>
|
||||
public sealed class DataSyncPageTests(PlaywrightFixture fixture) : UiTestBase(fixture)
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies the Data Sync page loads at /data-sync/requests and displays action buttons.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Steps:
|
||||
/// 1. Navigate to the Data Sync Requests page.
|
||||
/// 2. Assert the URL ends with /data-sync/requests or /search (redirect).
|
||||
/// 3. If on the data sync page, assert "Data Sync Requests" heading is visible.
|
||||
/// 4. Assert "New Request" or "Reload Pipelines" button is visible.
|
||||
/// 5. If redirected, assert "Search Details" is visible.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
[Trait("Category", "RequiresDockerHost")]
|
||||
public async Task DataSync_Loads()
|
||||
{
|
||||
await RunAsync(async page =>
|
||||
{
|
||||
await UiNavigationHelper.NavigateToDataSyncAsync(page);
|
||||
string url = page.Url;
|
||||
bool onDataSync = url.EndsWith("/data-sync/requests", StringComparison.OrdinalIgnoreCase);
|
||||
bool redirectedToSearch = url.EndsWith("/search", StringComparison.OrdinalIgnoreCase);
|
||||
Assert.True(onDataSync || redirectedToSearch, $"Unexpected URL: {url}");
|
||||
|
||||
if (onDataSync)
|
||||
{
|
||||
await Assertions.Expect(page.GetByText("Data Sync Requests"))
|
||||
.ToBeVisibleAsync(new LocatorAssertionsToBeVisibleOptions { Timeout = 15_000 });
|
||||
var newRequestButton =
|
||||
page.GetByRole(AriaRole.Button, new PageGetByRoleOptions { Name = "New Request" });
|
||||
var reloadButton =
|
||||
page.GetByRole(AriaRole.Button, new PageGetByRoleOptions { Name = "Reload Pipelines" });
|
||||
bool hasAnyControl = await newRequestButton.IsVisibleAsync() || await reloadButton.IsVisibleAsync();
|
||||
Assert.True(hasAnyControl, "Expected Data Sync action buttons to be visible.");
|
||||
}
|
||||
else
|
||||
{
|
||||
await Assertions.Expect(page.GetByText("Search Details"))
|
||||
.ToBeVisibleAsync(new LocatorAssertionsToBeVisibleOptions { Timeout = 15_000 });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
global using Xunit;
|
||||
global using Microsoft.Playwright;
|
||||
@@ -0,0 +1,45 @@
|
||||
namespace JdeScoping.Ui.Tests.Helpers;
|
||||
|
||||
internal static class UiAuthHelper
|
||||
{
|
||||
public static async Task LoginAsync(IPage page, string username = "testuser", string password = "testpass")
|
||||
{
|
||||
var loginForm = page.GetByText("Authentication Required");
|
||||
bool formVisible = await loginForm.IsVisibleAsync();
|
||||
if (!formVisible) return;
|
||||
|
||||
await page.Locator("input[name='Username']").FillAsync(username);
|
||||
await page.Locator("input[name='Password']").FillAsync(password);
|
||||
await page.Locator("button[type='submit']:has-text('LOGIN')").ClickAsync();
|
||||
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
await page.WaitForTimeoutAsync(3_000);
|
||||
await page.GotoAsync("/search");
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
|
||||
if (await loginForm.IsVisibleAsync())
|
||||
{
|
||||
var notifications = page.Locator(".rz-notification");
|
||||
int count = await notifications.CountAsync();
|
||||
var details = new List<string>();
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
string text = (await notifications.Nth(i).InnerTextAsync()).Trim();
|
||||
if (!string.IsNullOrWhiteSpace(text)) details.Add(text);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(
|
||||
$"Login did not complete. URL={page.Url}. Notifications={string.Join(" | ", details)}");
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task LogoutAsync(IPage page)
|
||||
{
|
||||
var logoutButton = page.GetByRole(AriaRole.Button, new PageGetByRoleOptions { Name = "Logout" });
|
||||
if (await logoutButton.IsVisibleAsync())
|
||||
{
|
||||
await logoutButton.ClickAsync();
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
namespace JdeScoping.Ui.Tests.Helpers;
|
||||
|
||||
internal static class UiNavigationHelper
|
||||
{
|
||||
public static async Task NavigateToSearchPageAsync(IPage page)
|
||||
{
|
||||
await page.GotoAsync("/search");
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
await UiAuthHelper.LoginAsync(page);
|
||||
await EnsureAuthenticatedOrThrowAsync(page);
|
||||
await WaitForBlazorReadyAsync(page);
|
||||
}
|
||||
|
||||
public static async Task NavigateToSearchesDashboardAsync(IPage page)
|
||||
{
|
||||
await page.GotoAsync("/searches");
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
await UiAuthHelper.LoginAsync(page);
|
||||
await EnsureAuthenticatedOrThrowAsync(page);
|
||||
await WaitForBlazorReadyAsync(page);
|
||||
}
|
||||
|
||||
public static async Task NavigateToQueueAsync(IPage page)
|
||||
{
|
||||
await page.GotoAsync("/search/queue");
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
await UiAuthHelper.LoginAsync(page);
|
||||
await EnsureAuthenticatedOrThrowAsync(page);
|
||||
await WaitForBlazorReadyAsync(page);
|
||||
}
|
||||
|
||||
public static async Task NavigateToRefreshStatusAsync(IPage page)
|
||||
{
|
||||
await page.GotoAsync("/refresh-status");
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
await UiAuthHelper.LoginAsync(page);
|
||||
await EnsureAuthenticatedOrThrowAsync(page);
|
||||
await WaitForBlazorReadyAsync(page);
|
||||
}
|
||||
|
||||
public static async Task NavigateToDataSyncAsync(IPage page)
|
||||
{
|
||||
await page.GotoAsync("/data-sync/requests");
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
await UiAuthHelper.LoginAsync(page);
|
||||
await EnsureAuthenticatedOrThrowAsync(page);
|
||||
await WaitForBlazorReadyAsync(page);
|
||||
}
|
||||
|
||||
public static async Task NavigateToLoginAsync(IPage page)
|
||||
{
|
||||
await page.GotoAsync("/login");
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
}
|
||||
|
||||
private static async Task WaitForBlazorReadyAsync(IPage page)
|
||||
{
|
||||
var timeoutMs = 15_000;
|
||||
try
|
||||
{
|
||||
await page.Locator(".rz-dropdown").First.WaitForAsync(new LocatorWaitForOptions
|
||||
{
|
||||
State = WaitForSelectorState.Visible,
|
||||
Timeout = timeoutMs
|
||||
});
|
||||
return;
|
||||
}
|
||||
catch (PlaywrightException)
|
||||
{
|
||||
// Try additional readiness markers.
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await page.Locator(".rz-data-grid").First.WaitForAsync(new LocatorWaitForOptions
|
||||
{
|
||||
State = WaitForSelectorState.Visible,
|
||||
Timeout = timeoutMs
|
||||
});
|
||||
return;
|
||||
}
|
||||
catch (PlaywrightException)
|
||||
{
|
||||
// Try text marker as final fallback.
|
||||
}
|
||||
|
||||
await page.GetByText("Search Details").First.WaitForAsync(new LocatorWaitForOptions
|
||||
{
|
||||
State = WaitForSelectorState.Visible,
|
||||
Timeout = timeoutMs
|
||||
});
|
||||
await page.WaitForTimeoutAsync(1_000);
|
||||
}
|
||||
|
||||
private static async Task EnsureAuthenticatedOrThrowAsync(IPage page)
|
||||
{
|
||||
var meResponse = await page.Context.APIRequest.GetAsync("/api/auth/me");
|
||||
if (meResponse.Status != 200)
|
||||
throw new InvalidOperationException(
|
||||
$"UI test host did not establish authenticated session after login. /api/auth/me status={meResponse.Status}.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
namespace JdeScoping.Ui.Tests.Helpers;
|
||||
|
||||
internal static class UiSearchFormHelper
|
||||
{
|
||||
public static async Task SelectSearchTypeAsync(IPage page, string searchType)
|
||||
{
|
||||
await page.Locator(".rz-dropdown").First.ClickAsync();
|
||||
await page.GetByRole(AriaRole.Option, new PageGetByRoleOptions { Name = searchType, Exact = true })
|
||||
.ClickAsync();
|
||||
await page.WaitForTimeoutAsync(500);
|
||||
}
|
||||
|
||||
public static Task EnterSearchNameAsync(IPage page, string name)
|
||||
{
|
||||
return page.Locator("input[placeholder=' ']").First.FillAsync(name);
|
||||
}
|
||||
|
||||
public static async Task SetDateRangeAsync(IPage page, string minimumMmDdYyyy, string maximumMmDdYyyy)
|
||||
{
|
||||
await page.Locator("input[name='MinimumDt']").FillAsync(minimumMmDdYyyy);
|
||||
await page.Locator("input[name='MaximumDt']").FillAsync(maximumMmDdYyyy);
|
||||
}
|
||||
|
||||
public static async Task AddAutocompleteItemAsync(IPage page, string panelHeader, string value)
|
||||
{
|
||||
var panel = page.Locator($".rz-card:has-text('{panelHeader}')");
|
||||
await panel.Locator(".rz-autocomplete input").First.FillAsync(value);
|
||||
await page.WaitForTimeoutAsync(500);
|
||||
|
||||
var listItem = page.Locator(".rz-autocomplete-list .rz-autocomplete-list-item").First;
|
||||
if (await listItem.IsVisibleAsync()) await listItem.ClickAsync();
|
||||
|
||||
await panel.GetByRole(AriaRole.Button, new LocatorGetByRoleOptions { Name = "Add" }).ClickAsync();
|
||||
await page.WaitForTimeoutAsync(250);
|
||||
}
|
||||
|
||||
public static async Task UploadFileAsync(IPage page, string panelHeader, string filePath)
|
||||
{
|
||||
var panel = page.Locator($".rz-card:has-text('{panelHeader}')");
|
||||
await panel.Locator("input[type='file']").First.SetInputFilesAsync(filePath);
|
||||
await page.WaitForTimeoutAsync(1_500);
|
||||
}
|
||||
|
||||
public static async Task SubmitSearchAsync(IPage page)
|
||||
{
|
||||
await page.GetByRole(AriaRole.Button, new PageGetByRoleOptions { Name = "Submit" }).First.ClickAsync();
|
||||
await page.GetByText("Confirm Submit").WaitForAsync(new LocatorWaitForOptions
|
||||
{
|
||||
State = WaitForSelectorState.Visible,
|
||||
Timeout = 10_000
|
||||
});
|
||||
await page.Locator(".rz-dialog-wrapper button")
|
||||
.GetByText("Submit", new LocatorGetByTextOptions { Exact = true }).ClickAsync();
|
||||
await page.WaitForTimeoutAsync(1_000);
|
||||
}
|
||||
|
||||
public static async Task AssertNoErrorNotificationAsync(IPage page)
|
||||
{
|
||||
var error = page.Locator(".rz-notification-error");
|
||||
await Assertions.Expect(error).Not
|
||||
.ToBeVisibleAsync(new LocatorAssertionsToBeVisibleOptions { Timeout = 5_000 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk"/>
|
||||
<PackageReference Include="Microsoft.Playwright"/>
|
||||
<PackageReference Include="Shouldly"/>
|
||||
<PackageReference Include="xunit"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\JdeScoping.Core\JdeScoping.Core.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="TestData/**/*" CopyToOutputDirectory="PreserveNewest"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,47 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using JdeScoping.Ui.Tests.Helpers;
|
||||
using JdeScoping.Ui.Tests.Support;
|
||||
|
||||
namespace JdeScoping.Ui.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Playwright UI tests for the Login page.
|
||||
/// Validates that the login form renders, credentials are accepted, and logout revokes the session.
|
||||
/// Requires a running Docker host (Category: RequiresDockerHost).
|
||||
/// </summary>
|
||||
public sealed class LoginPageTests(PlaywrightFixture fixture) : UiTestBase(fixture)
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies the login page renders, credentials authenticate the user, and logout revokes the session.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Steps:
|
||||
/// 1. Navigate to the login page.
|
||||
/// 2. Assert the page title contains "Login - JDE Scoping Tool".
|
||||
/// 3. Submit test credentials via UiAuthHelper.LoginAsync.
|
||||
/// 4. Assert the user sees the Logout button or remains on the login view.
|
||||
/// 5. Invoke UiAuthHelper.LogoutAsync.
|
||||
/// 6. GET /api/auth/me and assert HTTP 401 (session revoked).
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
[Trait("Category", "RequiresDockerHost")]
|
||||
public async Task LoginPage_AllowsLoginAndLogout()
|
||||
{
|
||||
await RunAsync(async page =>
|
||||
{
|
||||
await UiNavigationHelper.NavigateToLoginAsync(page);
|
||||
await Assertions.Expect(page).ToHaveTitleAsync(new Regex("Login - JDE Scoping Tool"));
|
||||
|
||||
await UiAuthHelper.LoginAsync(page);
|
||||
var loggedOutView = page.GetByText("Authentication Required");
|
||||
var logoutButton = page.GetByRole(AriaRole.Button, new PageGetByRoleOptions { Name = "Logout" });
|
||||
bool authenticated = await logoutButton.IsVisibleAsync();
|
||||
bool stillOnLogin = await loggedOutView.IsVisibleAsync();
|
||||
Assert.True(authenticated || stillOnLogin);
|
||||
|
||||
await UiAuthHelper.LogoutAsync(page);
|
||||
var meAfterLogout = await page.Context.APIRequest.GetAsync("/api/auth/me");
|
||||
Assert.Equal(401, meAfterLogout.Status);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
# JdeScoping.Ui.Tests
|
||||
|
||||
Playwright-for-.NET UI tests migrated from the legacy TypeScript Playwright suite.
|
||||
|
||||
## Preconditions
|
||||
|
||||
- Docker host container is already running via `/Users/dohertj2/Desktop/JdeScopingTool/NEW/deploy/docker/deploy-host.sh`
|
||||
- App reachable at `http://localhost:5294` (or set `JDESCOPING_UI_BASE_URL`)
|
||||
|
||||
## First-time setup
|
||||
|
||||
```bash
|
||||
cd /Users/dohertj2/Desktop/JdeScopingTool/NEW/tests/JdeScoping.Ui.Tests
|
||||
dotnet build
|
||||
pwsh bin/Debug/net10.0/playwright.ps1 install chromium
|
||||
```
|
||||
|
||||
## Run tests
|
||||
|
||||
```bash
|
||||
cd /Users/dohertj2/Desktop/JdeScopingTool/NEW
|
||||
dotnet test tests/JdeScoping.Ui.Tests/JdeScoping.Ui.Tests.csproj --filter "Category=RequiresDockerHost"
|
||||
```
|
||||
|
||||
Run headed mode:
|
||||
|
||||
```bash
|
||||
JDESCOPING_UI_HEADED=true dotnet test tests/JdeScoping.Ui.Tests/JdeScoping.Ui.Tests.csproj
|
||||
```
|
||||
@@ -0,0 +1,43 @@
|
||||
using JdeScoping.Ui.Tests.Helpers;
|
||||
using JdeScoping.Ui.Tests.Support;
|
||||
|
||||
namespace JdeScoping.Ui.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Playwright UI smoke tests for the Cache Refresh Status page.
|
||||
/// Validates that the page loads and shows the "Cache Refresh Status" heading or redirects to search.
|
||||
/// Requires a running Docker host (Category: RequiresDockerHost).
|
||||
/// </summary>
|
||||
public sealed class RefreshStatusPageTests(PlaywrightFixture fixture) : UiTestBase(fixture)
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies the Refresh Status page loads at /refresh-status and displays the expected heading.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Steps:
|
||||
/// 1. Navigate to the Refresh Status page.
|
||||
/// 2. Assert the URL ends with /refresh-status or /search (redirect).
|
||||
/// 3. If on the refresh page, assert "Cache Refresh Status" heading is visible.
|
||||
/// 4. If redirected, assert "Search Details" is visible.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
[Trait("Category", "RequiresDockerHost")]
|
||||
public async Task RefreshStatus_Loads()
|
||||
{
|
||||
await RunAsync(async page =>
|
||||
{
|
||||
await UiNavigationHelper.NavigateToRefreshStatusAsync(page);
|
||||
string url = page.Url;
|
||||
bool onRefreshStatus = url.EndsWith("/refresh-status", StringComparison.OrdinalIgnoreCase);
|
||||
bool redirectedToSearch = url.EndsWith("/search", StringComparison.OrdinalIgnoreCase);
|
||||
Assert.True(onRefreshStatus || redirectedToSearch, $"Unexpected URL: {url}");
|
||||
|
||||
if (onRefreshStatus)
|
||||
await Assertions.Expect(page.GetByText("Cache Refresh Status"))
|
||||
.ToBeVisibleAsync(new LocatorAssertionsToBeVisibleOptions { Timeout = 15_000 });
|
||||
else
|
||||
await Assertions.Expect(page.GetByText("Search Details"))
|
||||
.ToBeVisibleAsync(new LocatorAssertionsToBeVisibleOptions { Timeout = 15_000 });
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using JdeScoping.Ui.Tests.Helpers;
|
||||
using JdeScoping.Ui.Tests.Support;
|
||||
|
||||
namespace JdeScoping.Ui.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Playwright UI smoke tests for the Search page.
|
||||
/// Validates that the search form loads with primary controls visible and no error notifications.
|
||||
/// Requires a running Docker host (Category: RequiresDockerHost).
|
||||
/// </summary>
|
||||
public sealed class SearchPageTests(PlaywrightFixture fixture) : UiTestBase(fixture)
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies the search page loads and displays the "Search Details" heading, Submit button, and no errors.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Steps:
|
||||
/// 1. Navigate to the search page.
|
||||
/// 2. Assert the "Search Details" text is visible.
|
||||
/// 3. Assert the Submit button is visible.
|
||||
/// 4. Assert no error notification is present.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
[Trait("Category", "RequiresDockerHost")]
|
||||
public async Task SearchPage_LoadsAndShowsPrimaryControls()
|
||||
{
|
||||
await RunAsync(async page =>
|
||||
{
|
||||
await UiNavigationHelper.NavigateToSearchPageAsync(page);
|
||||
await Assertions.Expect(page.GetByText("Search Details")).ToBeVisibleAsync();
|
||||
await Assertions.Expect(page.GetByRole(AriaRole.Button, new PageGetByRoleOptions { Name = "Submit" }).First)
|
||||
.ToBeVisibleAsync();
|
||||
await UiSearchFormHelper.AssertNoErrorNotificationAsync(page);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using JdeScoping.Ui.Tests.Helpers;
|
||||
using JdeScoping.Ui.Tests.Support;
|
||||
|
||||
namespace JdeScoping.Ui.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Playwright UI smoke tests for the Search Queue page.
|
||||
/// Validates that the queue page loads and shows a data grid, alert, or loading indicator.
|
||||
/// Requires a running Docker host (Category: RequiresDockerHost).
|
||||
/// </summary>
|
||||
public sealed class SearchQueuePageTests(PlaywrightFixture fixture) : UiTestBase(fixture)
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies the Search Queue page loads at /search/queue and displays queue content or a redirect.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Steps:
|
||||
/// 1. Navigate to the Search Queue page.
|
||||
/// 2. Assert the URL ends with /search/queue or /search (redirect).
|
||||
/// 3. If on the queue page, assert "Search Queue" heading and grid/alert/loading indicator are visible.
|
||||
/// 4. If redirected, assert "Search Details" is visible.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
[Trait("Category", "RequiresDockerHost")]
|
||||
public async Task SearchQueue_Loads()
|
||||
{
|
||||
await RunAsync(async page =>
|
||||
{
|
||||
await UiNavigationHelper.NavigateToQueueAsync(page);
|
||||
string url = page.Url;
|
||||
bool onQueue = url.EndsWith("/search/queue", StringComparison.OrdinalIgnoreCase);
|
||||
bool redirectedToSearch = url.EndsWith("/search", StringComparison.OrdinalIgnoreCase);
|
||||
Assert.True(onQueue || redirectedToSearch, $"Unexpected URL: {url}");
|
||||
|
||||
if (onQueue)
|
||||
{
|
||||
await Assertions.Expect(page.GetByText("Search Queue"))
|
||||
.ToBeVisibleAsync(new LocatorAssertionsToBeVisibleOptions { Timeout = 15_000 });
|
||||
bool hasGrid = await page.Locator(".rz-data-grid").First.IsVisibleAsync();
|
||||
bool hasAlert = await page.Locator(".rz-alert").First.IsVisibleAsync();
|
||||
bool hasLoading = await page.GetByText("Loading queue").IsVisibleAsync();
|
||||
Assert.True(hasGrid || hasAlert || hasLoading, "Expected queue grid, alert, or loading indicator.");
|
||||
}
|
||||
else
|
||||
{
|
||||
await Assertions.Expect(page.GetByText("Search Details"))
|
||||
.ToBeVisibleAsync(new LocatorAssertionsToBeVisibleOptions { Timeout = 15_000 });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using JdeScoping.Ui.Tests.Helpers;
|
||||
using JdeScoping.Ui.Tests.Support;
|
||||
|
||||
namespace JdeScoping.Ui.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Playwright UI smoke tests for the Searches Dashboard page.
|
||||
/// Validates that the dashboard loads at the expected URL and shows a heading or data grid.
|
||||
/// Requires a running Docker host (Category: RequiresDockerHost).
|
||||
/// </summary>
|
||||
public sealed class SearchesDashboardPageTests(PlaywrightFixture fixture) : UiTestBase(fixture)
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies the Searches Dashboard page loads at /searches and displays a heading or data grid.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Steps:
|
||||
/// 1. Navigate to the Searches Dashboard page.
|
||||
/// 2. Assert the URL ends with /searches, /search, or /.
|
||||
/// 3. Assert that "Searches Dashboard", "Search Details", or the Radzen data grid is visible.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
[Trait("Category", "RequiresDockerHost")]
|
||||
public async Task SearchesDashboard_Loads()
|
||||
{
|
||||
await RunAsync(async page =>
|
||||
{
|
||||
await UiNavigationHelper.NavigateToSearchesDashboardAsync(page);
|
||||
string url = page.Url;
|
||||
Assert.True(
|
||||
url.EndsWith("/searches", StringComparison.OrdinalIgnoreCase) ||
|
||||
url.EndsWith("/search", StringComparison.OrdinalIgnoreCase) ||
|
||||
url.EndsWith("/", StringComparison.OrdinalIgnoreCase),
|
||||
$"Unexpected URL: {url}");
|
||||
|
||||
bool hasSearchesHeading = await page.GetByText("Searches Dashboard").IsVisibleAsync();
|
||||
bool hasSearchDetails = await page.GetByText("Search Details").IsVisibleAsync();
|
||||
bool hasGrid = await page.Locator(".rz-data-grid").First.IsVisibleAsync();
|
||||
Assert.True(hasSearchesHeading || hasSearchDetails || hasGrid);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
namespace JdeScoping.Ui.Tests.Support;
|
||||
|
||||
public sealed class PlaywrightFixture : IAsyncLifetime
|
||||
{
|
||||
private IBrowser? _browser;
|
||||
private IPlaywright? _playwright;
|
||||
|
||||
public IBrowser Browser => _browser ?? throw new InvalidOperationException("Browser is not initialized.");
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
_playwright = await Playwright.CreateAsync();
|
||||
_browser = await _playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
|
||||
{
|
||||
Headless = UiTestSettings.Headless,
|
||||
Args = ["--no-sandbox", "--disable-dev-shm-usage"]
|
||||
});
|
||||
}
|
||||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
if (_browser is not null) await _browser.CloseAsync();
|
||||
|
||||
_playwright?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using JdeScoping.Ui.Tests.Helpers;
|
||||
|
||||
namespace JdeScoping.Ui.Tests.Support;
|
||||
|
||||
public abstract class SearchFlowTestBase(PlaywrightFixture fixture) : UiTestBase(fixture)
|
||||
{
|
||||
private static bool StrictMode =>
|
||||
string.Equals(Environment.GetEnvironmentVariable("JDESCOPING_UI_STRICT"), "true",
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
protected Task RunSearchSubmissionAsync(
|
||||
string searchType,
|
||||
string testName,
|
||||
string? minDate = null,
|
||||
string? maxDate = null,
|
||||
(string PanelHeader, string Value)[]? autocompleteItems = null,
|
||||
(string PanelHeader, string FileName)[]? uploads = null)
|
||||
{
|
||||
return RunAsync(async page =>
|
||||
{
|
||||
await UiNavigationHelper.NavigateToSearchPageAsync(page);
|
||||
await UiSearchFormHelper.EnterSearchNameAsync(page, testName);
|
||||
await UiSearchFormHelper.SelectSearchTypeAsync(page, searchType);
|
||||
await Assertions.Expect(page.Locator(".rz-dropdown-label").First).ToContainTextAsync(searchType);
|
||||
|
||||
if (!StrictMode)
|
||||
// Default mode is smoke-only against local docker where source systems can be offline.
|
||||
return;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(minDate) && !string.IsNullOrWhiteSpace(maxDate))
|
||||
await UiSearchFormHelper.SetDateRangeAsync(page, minDate, maxDate);
|
||||
|
||||
if (autocompleteItems is not null)
|
||||
foreach (var item in autocompleteItems)
|
||||
await UiSearchFormHelper.AddAutocompleteItemAsync(page, item.PanelHeader, item.Value);
|
||||
|
||||
if (uploads is not null)
|
||||
foreach (var upload in uploads)
|
||||
await UiSearchFormHelper.UploadFileAsync(page, upload.PanelHeader,
|
||||
TestDataPaths.Get(upload.FileName));
|
||||
|
||||
await UiSearchFormHelper.SubmitSearchAsync(page);
|
||||
await UiSearchFormHelper.AssertNoErrorNotificationAsync(page);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace JdeScoping.Ui.Tests.Support;
|
||||
|
||||
internal static class TestDataPaths
|
||||
{
|
||||
public static string Get(string fileName)
|
||||
{
|
||||
return Path.Combine(AppContext.BaseDirectory, "TestData", fileName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace JdeScoping.Ui.Tests.Support;
|
||||
|
||||
internal static class UiSearchTypes
|
||||
{
|
||||
public const string WorkOrder = "Work Order";
|
||||
public const string ComponentLot = "Component Lot";
|
||||
public const string TimeSpanProfitCenter = "Time Span + Profit Center";
|
||||
public const string TimeSpanWorkCenter = "Time Span + Work Center";
|
||||
public const string TimeSpanOperator = "Time Span + Operator";
|
||||
public const string TimeSpanPcItem = "Time Span + Profit Center + Item Number";
|
||||
public const string TimeSpanPcPartOp = "Time Span + Profit Center + Item/Operation/MIS";
|
||||
public const string TimeSpanPcWoPartOp = "Time Span + Profit Center + Work Order + Item/Operation/MIS";
|
||||
public const string TimeSpanPcExtractMis = "Time Span + Profit Center + Extract MIS";
|
||||
public const string TimeSpanWcItem = "Time Span + Work Center + Item Number";
|
||||
public const string TimeSpanWcExtractMis = "Time Span + Work Center + Extract MIS";
|
||||
public const string TimeSpanWcPartOp = "Time Span + Work Center + Item/Operation/MIS";
|
||||
public const string TimeSpanWcWoPartOp = "Time Span + Work Center + Work Order + Item/Operation/MIS";
|
||||
public const string TimeSpanItem = "Time Span + Item Number";
|
||||
public const string TimeSpanWcOperator = "Time Span + Work Center + Operator";
|
||||
public const string TimeSpanPcOperator = "Time Span + Profit Center + Operator";
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace JdeScoping.Ui.Tests.Support;
|
||||
|
||||
[Collection(UiTestCollection.Name)]
|
||||
public abstract class UiTestBase
|
||||
{
|
||||
private readonly PlaywrightFixture _fixture;
|
||||
|
||||
protected UiTestBase(PlaywrightFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
}
|
||||
|
||||
protected async Task RunAsync(Func<IPage, Task> action)
|
||||
{
|
||||
await using var context = await _fixture.Browser.NewContextAsync(new BrowserNewContextOptions
|
||||
{
|
||||
BaseURL = UiTestSettings.BaseUrl,
|
||||
IgnoreHTTPSErrors = true
|
||||
});
|
||||
|
||||
var page = await context.NewPageAsync();
|
||||
page.SetDefaultTimeout(30_000);
|
||||
page.SetDefaultNavigationTimeout(120_000);
|
||||
|
||||
await action(page);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace JdeScoping.Ui.Tests.Support;
|
||||
|
||||
[CollectionDefinition(Name)]
|
||||
public sealed class UiTestCollection : ICollectionFixture<PlaywrightFixture>
|
||||
{
|
||||
public const string Name = "UiTests";
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace JdeScoping.Ui.Tests.Support;
|
||||
|
||||
internal static class UiTestSettings
|
||||
{
|
||||
public static string BaseUrl =>
|
||||
Environment.GetEnvironmentVariable("JDESCOPING_UI_BASE_URL")
|
||||
?? "http://localhost:5294";
|
||||
|
||||
public static bool Headless =>
|
||||
!string.Equals(Environment.GetEnvironmentVariable("JDESCOPING_UI_HEADED"), "true",
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
{
|
||||
"profitCenters": [
|
||||
{
|
||||
"code": "1AM",
|
||||
"description": "Profit Center 1AM"
|
||||
},
|
||||
{
|
||||
"code": "1BM",
|
||||
"description": "Profit Center 1BM"
|
||||
},
|
||||
{
|
||||
"code": "1CM",
|
||||
"description": "Profit Center 1CM"
|
||||
},
|
||||
{
|
||||
"code": "1PM",
|
||||
"description": "Profit Center 1PM"
|
||||
},
|
||||
{
|
||||
"code": "2DM",
|
||||
"description": "Profit Center 2DM"
|
||||
},
|
||||
{
|
||||
"code": "2SM",
|
||||
"description": "Profit Center 2SM"
|
||||
},
|
||||
{
|
||||
"code": "3TM",
|
||||
"description": "Profit Center 3TM"
|
||||
},
|
||||
{
|
||||
"code": "4IM",
|
||||
"description": "Profit Center 4IM"
|
||||
},
|
||||
{
|
||||
"code": "5SM",
|
||||
"description": "Profit Center 5SM"
|
||||
}
|
||||
],
|
||||
"workCenters": [
|
||||
{
|
||||
"code": "WC001",
|
||||
"description": "Work Center 001"
|
||||
},
|
||||
{
|
||||
"code": "WC002",
|
||||
"description": "Work Center 002"
|
||||
},
|
||||
{
|
||||
"code": "WC003",
|
||||
"description": "Work Center 003"
|
||||
}
|
||||
],
|
||||
"operators": [
|
||||
{
|
||||
"userId": "ADAMSSN",
|
||||
"fullName": "Adams, S N"
|
||||
},
|
||||
{
|
||||
"userId": "AGNEWA",
|
||||
"fullName": "Agnew, A"
|
||||
},
|
||||
{
|
||||
"userId": "AGNEWL",
|
||||
"fullName": "Agnew, L"
|
||||
},
|
||||
{
|
||||
"userId": "ALASMARB",
|
||||
"fullName": "Alasmar, B"
|
||||
},
|
||||
{
|
||||
"userId": "ALEXIUCG",
|
||||
"fullName": "Alexiuc, G"
|
||||
},
|
||||
{
|
||||
"userId": "ALLENHY",
|
||||
"fullName": "Allen, H Y"
|
||||
},
|
||||
{
|
||||
"userId": "ALLENNI",
|
||||
"fullName": "Allen, N I"
|
||||
},
|
||||
{
|
||||
"userId": "ALURUM",
|
||||
"fullName": "Aluru, M"
|
||||
},
|
||||
{
|
||||
"userId": "ALVESM1",
|
||||
"fullName": "Alves, M"
|
||||
},
|
||||
{
|
||||
"userId": "APONTEVE",
|
||||
"fullName": "Aponte, V E"
|
||||
}
|
||||
],
|
||||
"workOrders": [
|
||||
{
|
||||
"workOrderNumber": "99059700",
|
||||
"itemNumber": "00598004702"
|
||||
},
|
||||
{
|
||||
"workOrderNumber": "99002260",
|
||||
"itemNumber": "82070000028"
|
||||
},
|
||||
{
|
||||
"workOrderNumber": "99002259",
|
||||
"itemNumber": "82070000027"
|
||||
},
|
||||
{
|
||||
"workOrderNumber": "99002258",
|
||||
"itemNumber": "82070000019"
|
||||
},
|
||||
{
|
||||
"workOrderNumber": "99002257",
|
||||
"itemNumber": "82070000018"
|
||||
},
|
||||
{
|
||||
"workOrderNumber": "99002256",
|
||||
"itemNumber": "82070000017"
|
||||
},
|
||||
{
|
||||
"workOrderNumber": "99002255",
|
||||
"itemNumber": "00855140333"
|
||||
},
|
||||
{
|
||||
"workOrderNumber": "99002254",
|
||||
"itemNumber": "00855480834"
|
||||
},
|
||||
{
|
||||
"workOrderNumber": "99002252",
|
||||
"itemNumber": "82070000016"
|
||||
},
|
||||
{
|
||||
"workOrderNumber": "99002251",
|
||||
"itemNumber": "00855910448"
|
||||
},
|
||||
{
|
||||
"workOrderNumber": "99002250",
|
||||
"itemNumber": "82070000015"
|
||||
},
|
||||
{
|
||||
"workOrderNumber": "99002249",
|
||||
"itemNumber": "00855480834"
|
||||
},
|
||||
{
|
||||
"workOrderNumber": "99002248",
|
||||
"itemNumber": "00855910446"
|
||||
},
|
||||
{
|
||||
"workOrderNumber": "99002247",
|
||||
"itemNumber": "00855910447"
|
||||
},
|
||||
{
|
||||
"workOrderNumber": "99002246",
|
||||
"itemNumber": "82900171601"
|
||||
}
|
||||
],
|
||||
"itemNumbers": [
|
||||
{
|
||||
"itemNumber": "00598004702",
|
||||
"description": "Item 598004702"
|
||||
},
|
||||
{
|
||||
"itemNumber": "82070000028",
|
||||
"description": "Item 82070000028"
|
||||
},
|
||||
{
|
||||
"itemNumber": "82070000027",
|
||||
"description": "Item 82070000027"
|
||||
},
|
||||
{
|
||||
"itemNumber": "00855140333",
|
||||
"description": "Item 855140333"
|
||||
},
|
||||
{
|
||||
"itemNumber": "00855480834",
|
||||
"description": "Item 855480834"
|
||||
}
|
||||
],
|
||||
"componentLots": [
|
||||
{
|
||||
"lotNumber": "LOT001",
|
||||
"itemNumber": "00598004702"
|
||||
},
|
||||
{
|
||||
"lotNumber": "LOT002",
|
||||
"itemNumber": "82070000028"
|
||||
},
|
||||
{
|
||||
"lotNumber": "LOT003",
|
||||
"itemNumber": "82070000027"
|
||||
}
|
||||
],
|
||||
"partOperations": [
|
||||
{
|
||||
"itemNumber": "00598004702",
|
||||
"operationNumber": "100",
|
||||
"misNumber": "MIS001",
|
||||
"misRevision": "A"
|
||||
},
|
||||
{
|
||||
"itemNumber": "00598004702",
|
||||
"operationNumber": "200",
|
||||
"misNumber": "MIS002",
|
||||
"misRevision": "B"
|
||||
},
|
||||
{
|
||||
"itemNumber": "82070000028",
|
||||
"operationNumber": "100",
|
||||
"misNumber": "MIS003",
|
||||
"misRevision": "A"
|
||||
}
|
||||
],
|
||||
"dateRanges": {
|
||||
"recent": {
|
||||
"min": "2020-01-01",
|
||||
"max": "2020-09-01",
|
||||
"description": "Recent data range"
|
||||
},
|
||||
"midRange": {
|
||||
"min": "2018-01-01",
|
||||
"max": "2019-12-31",
|
||||
"description": "Mid-range data"
|
||||
},
|
||||
"historical": {
|
||||
"min": "2016-01-01",
|
||||
"max": "2017-12-31",
|
||||
"description": "Historical data"
|
||||
},
|
||||
"sameDay": {
|
||||
"min": "2020-06-15",
|
||||
"max": "2020-06-15",
|
||||
"description": "Single day range"
|
||||
},
|
||||
"startBoundary": {
|
||||
"min": "1905-01-20",
|
||||
"max": "1905-12-31",
|
||||
"description": "Start of data range"
|
||||
},
|
||||
"endBoundary": {
|
||||
"min": "2020-08-01",
|
||||
"max": "2020-09-01",
|
||||
"description": "End of data range"
|
||||
}
|
||||
},
|
||||
"invalidData": {
|
||||
"workOrders": {
|
||||
"invalidFormat": "ABC123XYZ",
|
||||
"specialChars": "99059700!@#",
|
||||
"empty": "",
|
||||
"whitespace": " "
|
||||
},
|
||||
"profitCenters": {
|
||||
"invalid": "INVALID",
|
||||
"specialChars": "1AM!@#",
|
||||
"empty": "",
|
||||
"tooLong": "1AMEXTRALONG"
|
||||
},
|
||||
"dates": {
|
||||
"invalidFormat": "31-12-2020",
|
||||
"future": {
|
||||
"min": "2025-01-01",
|
||||
"max": "2025-12-31"
|
||||
},
|
||||
"reversed": {
|
||||
"min": "2020-09-01",
|
||||
"max": "2020-01-01"
|
||||
}
|
||||
}
|
||||
},
|
||||
"testCredentials": {
|
||||
"validUser": {
|
||||
"username": "testuser",
|
||||
"password": "testpass"
|
||||
},
|
||||
"invalidUser": {
|
||||
"username": "invaliduser",
|
||||
"password": "wrongpassword"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using JdeScoping.Ui.Tests.Support;
|
||||
|
||||
namespace JdeScoping.Ui.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Playwright UI tests for the "Time Span + Item Number" search type (TC-140).
|
||||
/// Validates search form interaction in smoke mode and full submission with workbook upload in strict mode.
|
||||
/// Requires a running Docker host (Category: RequiresDockerHost).
|
||||
/// </summary>
|
||||
public sealed class TimeSpanItemNumberSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies the Time Span + Item Number search form submits with an uploaded workbook filter (TC-140).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Steps (smoke mode stops after step 4; strict mode runs all steps):
|
||||
/// 1. Navigate to the search page.
|
||||
/// 2. Enter the search name "MIGRATED-TC-140".
|
||||
/// 3. Select the "Time Span + Item Number" search type from the dropdown.
|
||||
/// 4. Verify the dropdown displays the selected type.
|
||||
/// 5. Set the date range to 01/01/2019 – 12/31/2019 (strict only).
|
||||
/// 6. Upload "single_item.xlsx" to the "Filter by Item Number" panel (strict only).
|
||||
/// 7. Click Submit (strict only).
|
||||
/// 8. Assert no error notification is present (strict only).
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
[Trait("Category", "RequiresDockerHost")]
|
||||
public Task TimeSpanItemNumber_Submits()
|
||||
{
|
||||
return RunSearchSubmissionAsync(
|
||||
UiSearchTypes.TimeSpanItem,
|
||||
"MIGRATED-TC-140",
|
||||
"01/01/2019",
|
||||
"12/31/2019",
|
||||
uploads:
|
||||
[
|
||||
("Filter by Item Number", "single_item.xlsx")
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using JdeScoping.Ui.Tests.Support;
|
||||
|
||||
namespace JdeScoping.Ui.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Playwright UI tests for the "Time Span + Operator" search type (TC-050).
|
||||
/// Validates search form interaction in smoke mode and full submission in strict mode.
|
||||
/// Requires a running Docker host (Category: RequiresDockerHost).
|
||||
/// </summary>
|
||||
public sealed class TimeSpanOperatorSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies the Time Span + Operator search form submits with autocomplete filter (TC-050).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Steps (smoke mode stops after step 4; strict mode runs all steps):
|
||||
/// 1. Navigate to the search page.
|
||||
/// 2. Enter the search name "MIGRATED-TC-050".
|
||||
/// 3. Select the "Time Span + Operator" search type from the dropdown.
|
||||
/// 4. Verify the dropdown displays the selected type.
|
||||
/// 5. Set the date range to 01/01/2019 – 12/31/2019 (strict only).
|
||||
/// 6. Add autocomplete value "ADAMSSN" to the "Filter by Operator" panel (strict only).
|
||||
/// 7. Click Submit (strict only).
|
||||
/// 8. Assert no error notification is present (strict only).
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
[Trait("Category", "RequiresDockerHost")]
|
||||
public Task TimeSpanOperator_Submits()
|
||||
{
|
||||
return RunSearchSubmissionAsync(
|
||||
UiSearchTypes.TimeSpanOperator,
|
||||
"MIGRATED-TC-050",
|
||||
"01/01/2019",
|
||||
"12/31/2019",
|
||||
[
|
||||
("Filter by Operator", "ADAMSSN")
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using JdeScoping.Ui.Tests.Support;
|
||||
|
||||
namespace JdeScoping.Ui.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Playwright UI tests for the "Time Span + Profit Center + Extract MIS" search type (TC-090).
|
||||
/// Validates search form interaction in smoke mode and full submission in strict mode.
|
||||
/// Requires a running Docker host (Category: RequiresDockerHost).
|
||||
/// </summary>
|
||||
public sealed class TimeSpanPcExtractMisSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies the Time Span + Profit Center + Extract MIS search form submits with autocomplete filter (TC-090).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Steps (smoke mode stops after step 4; strict mode runs all steps):
|
||||
/// 1. Navigate to the search page.
|
||||
/// 2. Enter the search name "MIGRATED-TC-090".
|
||||
/// 3. Select the "Time Span + Profit Center + Extract MIS" search type from the dropdown.
|
||||
/// 4. Verify the dropdown displays the selected type.
|
||||
/// 5. Set the date range to 01/01/2019 – 12/31/2019 (strict only).
|
||||
/// 6. Add autocomplete value "1AM" to the "Filter by Profit Center" panel (strict only).
|
||||
/// 7. Click Submit (strict only).
|
||||
/// 8. Assert no error notification is present (strict only).
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
[Trait("Category", "RequiresDockerHost")]
|
||||
public Task TimeSpanPcExtractMis_Submits()
|
||||
{
|
||||
return RunSearchSubmissionAsync(
|
||||
UiSearchTypes.TimeSpanPcExtractMis,
|
||||
"MIGRATED-TC-090",
|
||||
"01/01/2019",
|
||||
"12/31/2019",
|
||||
[
|
||||
("Filter by Profit Center", "1AM")
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using JdeScoping.Ui.Tests.Support;
|
||||
|
||||
namespace JdeScoping.Ui.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Playwright UI tests for the "Time Span + Profit Center + Item Number" search type (TC-060).
|
||||
/// Validates search form interaction in smoke mode and full submission in strict mode.
|
||||
/// Requires a running Docker host (Category: RequiresDockerHost).
|
||||
/// </summary>
|
||||
public sealed class TimeSpanPcItemSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies the Time Span + Profit Center + Item Number search form submits with autocomplete and upload (TC-060).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Steps (smoke mode stops after step 4; strict mode runs all steps):
|
||||
/// 1. Navigate to the search page.
|
||||
/// 2. Enter the search name "MIGRATED-TC-060".
|
||||
/// 3. Select the "Time Span + Profit Center + Item Number" search type from the dropdown.
|
||||
/// 4. Verify the dropdown displays the selected type.
|
||||
/// 5. Set the date range to 01/01/2019 – 12/31/2019 (strict only).
|
||||
/// 6. Add autocomplete value "1AM" to the "Filter by Profit Center" panel (strict only).
|
||||
/// 7. Upload "single_item.xlsx" to the "Filter by Item Number" panel (strict only).
|
||||
/// 8. Click Submit (strict only).
|
||||
/// 9. Assert no error notification is present (strict only).
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
[Trait("Category", "RequiresDockerHost")]
|
||||
public Task TimeSpanPcItem_Submits()
|
||||
{
|
||||
return RunSearchSubmissionAsync(
|
||||
UiSearchTypes.TimeSpanPcItem,
|
||||
"MIGRATED-TC-060",
|
||||
"01/01/2019",
|
||||
"12/31/2019",
|
||||
[
|
||||
("Filter by Profit Center", "1AM")
|
||||
],
|
||||
[
|
||||
("Filter by Item Number", "single_item.xlsx")
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using JdeScoping.Ui.Tests.Support;
|
||||
|
||||
namespace JdeScoping.Ui.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Playwright UI tests for the "Time Span + Profit Center + Operator" search type (TC-160).
|
||||
/// Validates search form interaction in smoke mode and full submission in strict mode.
|
||||
/// Requires a running Docker host (Category: RequiresDockerHost).
|
||||
/// </summary>
|
||||
public sealed class TimeSpanPcOperatorSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies the Time Span + Profit Center + Operator search form submits with autocomplete filters (TC-160).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Steps (smoke mode stops after step 4; strict mode runs all steps):
|
||||
/// 1. Navigate to the search page.
|
||||
/// 2. Enter the search name "MIGRATED-TC-160".
|
||||
/// 3. Select the "Time Span + Profit Center + Operator" search type from the dropdown.
|
||||
/// 4. Verify the dropdown displays the selected type.
|
||||
/// 5. Set the date range to 01/01/2019 – 12/31/2019 (strict only).
|
||||
/// 6. Add autocomplete value "1AM" to the "Filter by Profit Center" panel (strict only).
|
||||
/// 7. Add autocomplete value "ADAMSSN" to the "Filter by Operator" panel (strict only).
|
||||
/// 8. Click Submit (strict only).
|
||||
/// 9. Assert no error notification is present (strict only).
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
[Trait("Category", "RequiresDockerHost")]
|
||||
public Task TimeSpanPcOperator_Submits()
|
||||
{
|
||||
return RunSearchSubmissionAsync(
|
||||
UiSearchTypes.TimeSpanPcOperator,
|
||||
"MIGRATED-TC-160",
|
||||
"01/01/2019",
|
||||
"12/31/2019",
|
||||
[
|
||||
("Filter by Profit Center", "1AM"),
|
||||
("Filter by Operator", "ADAMSSN")
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using JdeScoping.Ui.Tests.Support;
|
||||
|
||||
namespace JdeScoping.Ui.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Playwright UI tests for the "Time Span + Profit Center + Item/Operation/MIS" search type (TC-070).
|
||||
/// Validates search form interaction in smoke mode and full submission in strict mode.
|
||||
/// Requires a running Docker host (Category: RequiresDockerHost).
|
||||
/// </summary>
|
||||
public sealed class TimeSpanPcPartOpSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies the Time Span + Profit Center + Item/Operation/MIS search form submits with autocomplete and upload (TC-070).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Steps (smoke mode stops after step 4; strict mode runs all steps):
|
||||
/// 1. Navigate to the search page.
|
||||
/// 2. Enter the search name "MIGRATED-TC-070".
|
||||
/// 3. Select the "Time Span + Profit Center + Item/Operation/MIS" search type from the dropdown.
|
||||
/// 4. Verify the dropdown displays the selected type.
|
||||
/// 5. Set the date range to 01/01/2019 – 12/31/2019 (strict only).
|
||||
/// 6. Add autocomplete value "1AM" to the "Filter by Profit Center" panel (strict only).
|
||||
/// 7. Upload "single_operation.xlsx" to the "Filter By Item/Operation/MIS" panel (strict only).
|
||||
/// 8. Click Submit (strict only).
|
||||
/// 9. Assert no error notification is present (strict only).
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
[Trait("Category", "RequiresDockerHost")]
|
||||
public Task TimeSpanPcPartOp_Submits()
|
||||
{
|
||||
return RunSearchSubmissionAsync(
|
||||
UiSearchTypes.TimeSpanPcPartOp,
|
||||
"MIGRATED-TC-070",
|
||||
"01/01/2019",
|
||||
"12/31/2019",
|
||||
[
|
||||
("Filter by Profit Center", "1AM")
|
||||
],
|
||||
[
|
||||
("Filter By Item/Operation/MIS", "single_operation.xlsx")
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using JdeScoping.Ui.Tests.Support;
|
||||
|
||||
namespace JdeScoping.Ui.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Playwright UI tests for the "Time Span + Profit Center + Work Order + Item/Operation/MIS" search type (TC-080).
|
||||
/// Validates search form interaction in smoke mode and full submission in strict mode.
|
||||
/// Requires a running Docker host (Category: RequiresDockerHost).
|
||||
/// </summary>
|
||||
public sealed class TimeSpanPcWoPartOpSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies the Time Span + PC + Work Order + Item/Op/MIS search form submits with autocomplete and uploads (TC-080).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Steps (smoke mode stops after step 4; strict mode runs all steps):
|
||||
/// 1. Navigate to the search page.
|
||||
/// 2. Enter the search name "MIGRATED-TC-080".
|
||||
/// 3. Select the "Time Span + Profit Center + Work Order + Item/Operation/MIS" search type from the dropdown.
|
||||
/// 4. Verify the dropdown displays the selected type.
|
||||
/// 5. Set the date range to 01/01/2019 – 12/31/2019 (strict only).
|
||||
/// 6. Add autocomplete value "1AM" to the "Filter by Profit Center" panel (strict only).
|
||||
/// 7. Upload "single_workorder.xlsx" to the "Filter by Work Order" panel (strict only).
|
||||
/// 8. Upload "single_operation.xlsx" to the "Filter By Item/Operation/MIS" panel (strict only).
|
||||
/// 9. Click Submit (strict only).
|
||||
/// 10. Assert no error notification is present (strict only).
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
[Trait("Category", "RequiresDockerHost")]
|
||||
public Task TimeSpanPcWoPartOp_Submits()
|
||||
{
|
||||
return RunSearchSubmissionAsync(
|
||||
UiSearchTypes.TimeSpanPcWoPartOp,
|
||||
"MIGRATED-TC-080",
|
||||
"01/01/2019",
|
||||
"12/31/2019",
|
||||
[
|
||||
("Filter by Profit Center", "1AM")
|
||||
],
|
||||
[
|
||||
("Filter by Work Order", "single_workorder.xlsx"),
|
||||
("Filter By Item/Operation/MIS", "single_operation.xlsx")
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using JdeScoping.Ui.Tests.Support;
|
||||
|
||||
namespace JdeScoping.Ui.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Playwright UI tests for the "Time Span + Profit Center" search type (TC-030).
|
||||
/// Validates search form interaction in smoke mode and full submission in strict mode.
|
||||
/// Requires a running Docker host (Category: RequiresDockerHost).
|
||||
/// </summary>
|
||||
public sealed class TimeSpanProfitCenterSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies the Time Span + Profit Center search form submits with autocomplete filter (TC-030).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Steps (smoke mode stops after step 4; strict mode runs all steps):
|
||||
/// 1. Navigate to the search page.
|
||||
/// 2. Enter the search name "MIGRATED-TC-030".
|
||||
/// 3. Select the "Time Span + Profit Center" search type from the dropdown.
|
||||
/// 4. Verify the dropdown displays the selected type.
|
||||
/// 5. Set the date range to 01/01/2019 – 12/31/2019 (strict only).
|
||||
/// 6. Add autocomplete value "1AM" to the "Filter by Profit Center" panel (strict only).
|
||||
/// 7. Click Submit (strict only).
|
||||
/// 8. Assert no error notification is present (strict only).
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
[Trait("Category", "RequiresDockerHost")]
|
||||
public Task TimeSpanProfitCenter_Submits()
|
||||
{
|
||||
return RunSearchSubmissionAsync(
|
||||
UiSearchTypes.TimeSpanProfitCenter,
|
||||
"MIGRATED-TC-030",
|
||||
"01/01/2019",
|
||||
"12/31/2019",
|
||||
[
|
||||
("Filter by Profit Center", "1AM")
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using JdeScoping.Ui.Tests.Support;
|
||||
|
||||
namespace JdeScoping.Ui.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Playwright UI tests for the "Time Span + Work Center + Extract MIS" search type (TC-110).
|
||||
/// Validates search form interaction in smoke mode and full submission in strict mode.
|
||||
/// Requires a running Docker host (Category: RequiresDockerHost).
|
||||
/// </summary>
|
||||
public sealed class TimeSpanWcExtractMisSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies the Time Span + Work Center + Extract MIS search form submits with autocomplete filter (TC-110).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Steps (smoke mode stops after step 4; strict mode runs all steps):
|
||||
/// 1. Navigate to the search page.
|
||||
/// 2. Enter the search name "MIGRATED-TC-110".
|
||||
/// 3. Select the "Time Span + Work Center + Extract MIS" search type from the dropdown.
|
||||
/// 4. Verify the dropdown displays the selected type.
|
||||
/// 5. Set the date range to 01/01/2019 – 12/31/2019 (strict only).
|
||||
/// 6. Add autocomplete value "0083AS" to the "Filter by Work Center" panel (strict only).
|
||||
/// 7. Click Submit (strict only).
|
||||
/// 8. Assert no error notification is present (strict only).
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
[Trait("Category", "RequiresDockerHost")]
|
||||
public Task TimeSpanWcExtractMis_Submits()
|
||||
{
|
||||
return RunSearchSubmissionAsync(
|
||||
UiSearchTypes.TimeSpanWcExtractMis,
|
||||
"MIGRATED-TC-110",
|
||||
"01/01/2019",
|
||||
"12/31/2019",
|
||||
[
|
||||
("Filter by Work Center", "0083AS")
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using JdeScoping.Ui.Tests.Support;
|
||||
|
||||
namespace JdeScoping.Ui.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Playwright UI tests for the "Time Span + Work Center + Item Number" search type (TC-100).
|
||||
/// Validates search form interaction in smoke mode and full submission in strict mode.
|
||||
/// Requires a running Docker host (Category: RequiresDockerHost).
|
||||
/// </summary>
|
||||
public sealed class TimeSpanWcItemSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies the Time Span + Work Center + Item Number search form submits with autocomplete and upload (TC-100).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Steps (smoke mode stops after step 4; strict mode runs all steps):
|
||||
/// 1. Navigate to the search page.
|
||||
/// 2. Enter the search name "MIGRATED-TC-100".
|
||||
/// 3. Select the "Time Span + Work Center + Item Number" search type from the dropdown.
|
||||
/// 4. Verify the dropdown displays the selected type.
|
||||
/// 5. Set the date range to 01/01/2019 – 12/31/2019 (strict only).
|
||||
/// 6. Add autocomplete value "0083AS" to the "Filter by Work Center" panel (strict only).
|
||||
/// 7. Upload "single_item.xlsx" to the "Filter by Item Number" panel (strict only).
|
||||
/// 8. Click Submit (strict only).
|
||||
/// 9. Assert no error notification is present (strict only).
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
[Trait("Category", "RequiresDockerHost")]
|
||||
public Task TimeSpanWcItem_Submits()
|
||||
{
|
||||
return RunSearchSubmissionAsync(
|
||||
UiSearchTypes.TimeSpanWcItem,
|
||||
"MIGRATED-TC-100",
|
||||
"01/01/2019",
|
||||
"12/31/2019",
|
||||
[
|
||||
("Filter by Work Center", "0083AS")
|
||||
],
|
||||
[
|
||||
("Filter by Item Number", "single_item.xlsx")
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using JdeScoping.Ui.Tests.Support;
|
||||
|
||||
namespace JdeScoping.Ui.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Playwright UI tests for the "Time Span + Work Center + Operator" search type (TC-150).
|
||||
/// Validates search form interaction in smoke mode and full submission in strict mode.
|
||||
/// Requires a running Docker host (Category: RequiresDockerHost).
|
||||
/// </summary>
|
||||
public sealed class TimeSpanWcOperatorSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies the Time Span + Work Center + Operator search form submits with autocomplete filters (TC-150).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Steps (smoke mode stops after step 4; strict mode runs all steps):
|
||||
/// 1. Navigate to the search page.
|
||||
/// 2. Enter the search name "MIGRATED-TC-150".
|
||||
/// 3. Select the "Time Span + Work Center + Operator" search type from the dropdown.
|
||||
/// 4. Verify the dropdown displays the selected type.
|
||||
/// 5. Set the date range to 01/01/2019 – 12/31/2019 (strict only).
|
||||
/// 6. Add autocomplete value "0083AS" to the "Filter by Work Center" panel (strict only).
|
||||
/// 7. Add autocomplete value "ADAMSSN" to the "Filter by Operator" panel (strict only).
|
||||
/// 8. Click Submit (strict only).
|
||||
/// 9. Assert no error notification is present (strict only).
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
[Trait("Category", "RequiresDockerHost")]
|
||||
public Task TimeSpanWcOperator_Submits()
|
||||
{
|
||||
return RunSearchSubmissionAsync(
|
||||
UiSearchTypes.TimeSpanWcOperator,
|
||||
"MIGRATED-TC-150",
|
||||
"01/01/2019",
|
||||
"12/31/2019",
|
||||
[
|
||||
("Filter by Work Center", "0083AS"),
|
||||
("Filter by Operator", "ADAMSSN")
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using JdeScoping.Ui.Tests.Support;
|
||||
|
||||
namespace JdeScoping.Ui.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Playwright UI tests for the "Time Span + Work Center + Item/Operation/MIS" search type (TC-120).
|
||||
/// Validates search form interaction in smoke mode and full submission in strict mode.
|
||||
/// Requires a running Docker host (Category: RequiresDockerHost).
|
||||
/// </summary>
|
||||
public sealed class TimeSpanWcPartOpSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies the Time Span + Work Center + Item/Operation/MIS search form submits with autocomplete and upload (TC-120).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Steps (smoke mode stops after step 4; strict mode runs all steps):
|
||||
/// 1. Navigate to the search page.
|
||||
/// 2. Enter the search name "MIGRATED-TC-120".
|
||||
/// 3. Select the "Time Span + Work Center + Item/Operation/MIS" search type from the dropdown.
|
||||
/// 4. Verify the dropdown displays the selected type.
|
||||
/// 5. Set the date range to 01/01/2019 – 12/31/2019 (strict only).
|
||||
/// 6. Add autocomplete value "0083AS" to the "Filter by Work Center" panel (strict only).
|
||||
/// 7. Upload "single_operation.xlsx" to the "Filter By Item/Operation/MIS" panel (strict only).
|
||||
/// 8. Click Submit (strict only).
|
||||
/// 9. Assert no error notification is present (strict only).
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
[Trait("Category", "RequiresDockerHost")]
|
||||
public Task TimeSpanWcPartOp_Submits()
|
||||
{
|
||||
return RunSearchSubmissionAsync(
|
||||
UiSearchTypes.TimeSpanWcPartOp,
|
||||
"MIGRATED-TC-120",
|
||||
"01/01/2019",
|
||||
"12/31/2019",
|
||||
[
|
||||
("Filter by Work Center", "0083AS")
|
||||
],
|
||||
[
|
||||
("Filter By Item/Operation/MIS", "single_operation.xlsx")
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using JdeScoping.Ui.Tests.Support;
|
||||
|
||||
namespace JdeScoping.Ui.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Playwright UI tests for the "Time Span + Work Center + Work Order + Item/Operation/MIS" search type (TC-130).
|
||||
/// Validates search form interaction in smoke mode and full submission in strict mode.
|
||||
/// Requires a running Docker host (Category: RequiresDockerHost).
|
||||
/// </summary>
|
||||
public sealed class TimeSpanWcWoPartOpSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies the Time Span + WC + Work Order + Item/Op/MIS search form submits with autocomplete and uploads (TC-130).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Steps (smoke mode stops after step 4; strict mode runs all steps):
|
||||
/// 1. Navigate to the search page.
|
||||
/// 2. Enter the search name "MIGRATED-TC-130".
|
||||
/// 3. Select the "Time Span + Work Center + Work Order + Item/Operation/MIS" search type from the dropdown.
|
||||
/// 4. Verify the dropdown displays the selected type.
|
||||
/// 5. Set the date range to 01/01/2019 – 12/31/2019 (strict only).
|
||||
/// 6. Add autocomplete value "0083AS" to the "Filter by Work Center" panel (strict only).
|
||||
/// 7. Upload "single_workorder.xlsx" to the "Filter by Work Order" panel (strict only).
|
||||
/// 8. Upload "single_operation.xlsx" to the "Filter By Item/Operation/MIS" panel (strict only).
|
||||
/// 9. Click Submit (strict only).
|
||||
/// 10. Assert no error notification is present (strict only).
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
[Trait("Category", "RequiresDockerHost")]
|
||||
public Task TimeSpanWcWoPartOp_Submits()
|
||||
{
|
||||
return RunSearchSubmissionAsync(
|
||||
UiSearchTypes.TimeSpanWcWoPartOp,
|
||||
"MIGRATED-TC-130",
|
||||
"01/01/2019",
|
||||
"12/31/2019",
|
||||
[
|
||||
("Filter by Work Center", "0083AS")
|
||||
],
|
||||
[
|
||||
("Filter by Work Order", "single_workorder.xlsx"),
|
||||
("Filter By Item/Operation/MIS", "single_operation.xlsx")
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using JdeScoping.Ui.Tests.Support;
|
||||
|
||||
namespace JdeScoping.Ui.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Playwright UI tests for the "Time Span + Work Center" search type (TC-040).
|
||||
/// Validates search form interaction in smoke mode and full submission in strict mode.
|
||||
/// Requires a running Docker host (Category: RequiresDockerHost).
|
||||
/// </summary>
|
||||
public sealed class TimeSpanWorkCenterSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies the Time Span + Work Center search form submits with autocomplete filter (TC-040).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Steps (smoke mode stops after step 4; strict mode runs all steps):
|
||||
/// 1. Navigate to the search page.
|
||||
/// 2. Enter the search name "MIGRATED-TC-040".
|
||||
/// 3. Select the "Time Span + Work Center" search type from the dropdown.
|
||||
/// 4. Verify the dropdown displays the selected type.
|
||||
/// 5. Set the date range to 01/01/2019 – 12/31/2019 (strict only).
|
||||
/// 6. Add autocomplete value "0083AS" to the "Filter by Work Center" panel (strict only).
|
||||
/// 7. Click Submit (strict only).
|
||||
/// 8. Assert no error notification is present (strict only).
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
[Trait("Category", "RequiresDockerHost")]
|
||||
public Task TimeSpanWorkCenter_Submits()
|
||||
{
|
||||
return RunSearchSubmissionAsync(
|
||||
UiSearchTypes.TimeSpanWorkCenter,
|
||||
"MIGRATED-TC-040",
|
||||
"01/01/2019",
|
||||
"12/31/2019",
|
||||
[
|
||||
("Filter by Work Center", "0083AS")
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using JdeScoping.Ui.Tests.Support;
|
||||
|
||||
namespace JdeScoping.Ui.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Playwright UI tests for the "Work Order" search type (TC-010).
|
||||
/// Validates search form interaction in smoke mode and full submission with workbook upload in strict mode.
|
||||
/// Requires a running Docker host (Category: RequiresDockerHost).
|
||||
/// </summary>
|
||||
public sealed class WorkOrderSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies the Work Order search form submits with an uploaded workbook filter (TC-010).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Steps (smoke mode stops after step 4; strict mode runs all steps):
|
||||
/// 1. Navigate to the search page.
|
||||
/// 2. Enter the search name "MIGRATED-TC-010".
|
||||
/// 3. Select the "Work Order" search type from the dropdown.
|
||||
/// 4. Verify the dropdown displays the selected type.
|
||||
/// 5. Upload "single_workorder.xlsx" to the "Filter by Work Order" panel (strict only).
|
||||
/// 6. Click Submit (strict only).
|
||||
/// 7. Assert no error notification is present (strict only).
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
[Trait("Category", "RequiresDockerHost")]
|
||||
public Task WorkOrder_SubmitsWithUploadedWorkbook()
|
||||
{
|
||||
return RunSearchSubmissionAsync(
|
||||
UiSearchTypes.WorkOrder,
|
||||
"MIGRATED-TC-010",
|
||||
uploads:
|
||||
[
|
||||
("Filter by Work Order", "single_workorder.xlsx")
|
||||
]);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user