Refine cockpit navigation and HR access
This commit is contained in:
@@ -12,14 +12,10 @@ public interface IDashboardPageService
|
||||
public sealed class DashboardPageService : IDashboardPageService
|
||||
{
|
||||
private readonly IDbContextFactory<AppDbContext> _dbFactory;
|
||||
private readonly IFinanceReconciliationService _financeReconciliationService;
|
||||
|
||||
public DashboardPageService(
|
||||
IDbContextFactory<AppDbContext> dbFactory,
|
||||
IFinanceReconciliationService financeReconciliationService)
|
||||
public DashboardPageService(IDbContextFactory<AppDbContext> dbFactory)
|
||||
{
|
||||
_dbFactory = dbFactory;
|
||||
_financeReconciliationService = financeReconciliationService;
|
||||
}
|
||||
|
||||
public async Task<DashboardPageState> LoadAsync()
|
||||
@@ -69,8 +65,7 @@ public sealed class DashboardPageService : IDashboardPageService
|
||||
return new DashboardPageState
|
||||
{
|
||||
DashboardRows = rows,
|
||||
ConsolidatedRows = BuildConsolidatedRows(await db.ExportSettings.FirstOrDefaultAsync() ?? new()),
|
||||
NetSalesReferenceRows = await _financeReconciliationService.BuildNetSalesReferenceRowsAsync(2025)
|
||||
ConsolidatedRows = BuildConsolidatedRows(await db.ExportSettings.FirstOrDefaultAsync() ?? new())
|
||||
};
|
||||
}
|
||||
|
||||
@@ -119,7 +114,6 @@ public sealed class DashboardPageState
|
||||
{
|
||||
public List<DashboardRow> DashboardRows { get; set; } = [];
|
||||
public List<ConsolidatedDashboardRow> ConsolidatedRows { get; set; } = [];
|
||||
public List<NetSalesReferenceRow> NetSalesReferenceRows { get; set; } = [];
|
||||
}
|
||||
|
||||
public sealed class DashboardRow
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Options;
|
||||
using TrafagSalesExporter.Security;
|
||||
|
||||
namespace TrafagSalesExporter.Services;
|
||||
|
||||
public interface IHrKpiAccessService
|
||||
{
|
||||
bool IsEnabled { get; }
|
||||
bool IsConfigured { get; }
|
||||
bool IsUnlocked { get; }
|
||||
bool TryUnlock(string username, string password);
|
||||
void Lock();
|
||||
}
|
||||
|
||||
public sealed class HrKpiAccessService : IHrKpiAccessService
|
||||
{
|
||||
private readonly HrKpiAccessOptions _options;
|
||||
|
||||
public HrKpiAccessService(IOptions<HrKpiAccessOptions> options)
|
||||
{
|
||||
_options = options.Value;
|
||||
}
|
||||
|
||||
public bool IsEnabled => _options.Enabled;
|
||||
|
||||
public bool IsConfigured =>
|
||||
!IsEnabled ||
|
||||
!string.IsNullOrWhiteSpace(_options.Username) &&
|
||||
(!string.IsNullOrWhiteSpace(_options.PasswordHash) || !string.IsNullOrEmpty(_options.Password));
|
||||
|
||||
public bool IsUnlocked { get; private set; }
|
||||
|
||||
public bool TryUnlock(string username, string password)
|
||||
{
|
||||
if (!IsEnabled)
|
||||
{
|
||||
IsUnlocked = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!IsConfigured ||
|
||||
string.IsNullOrWhiteSpace(username) ||
|
||||
string.IsNullOrEmpty(password) ||
|
||||
!FixedEquals(username.Trim(), _options.Username.Trim()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var valid = !string.IsNullOrWhiteSpace(_options.PasswordHash)
|
||||
? VerifyPasswordHash(password, _options.PasswordHash)
|
||||
: FixedEquals(password, _options.Password);
|
||||
|
||||
IsUnlocked = valid;
|
||||
return valid;
|
||||
}
|
||||
|
||||
public void Lock() => IsUnlocked = false;
|
||||
|
||||
private static bool VerifyPasswordHash(string password, string configuredHash)
|
||||
{
|
||||
var passwordHash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(password)));
|
||||
return FixedEquals(passwordHash, configuredHash.Trim());
|
||||
}
|
||||
|
||||
private static bool FixedEquals(string left, string right)
|
||||
{
|
||||
var leftBytes = Encoding.UTF8.GetBytes(left);
|
||||
var rightBytes = Encoding.UTF8.GetBytes(right);
|
||||
return leftBytes.Length == rightBytes.Length &&
|
||||
CryptographicOperations.FixedTimeEquals(leftBytes, rightBytes);
|
||||
}
|
||||
}
|
||||
@@ -199,14 +199,17 @@ public class ManagementCockpitService : IManagementCockpitService
|
||||
.Select(row => BuildCentralAggregationRow(row, aggregation))
|
||||
.ToList();
|
||||
|
||||
var selectedRows = aggregatedRows
|
||||
var scopedRows = ApplyCentralDimensionFilters(aggregatedRows, options)
|
||||
.ToList();
|
||||
|
||||
var selectedRows = scopedRows
|
||||
.Where(r => r.PeriodDate.Year == year && (!month.HasValue || r.PeriodDate.Month == month.Value))
|
||||
.ToList();
|
||||
|
||||
if (selectedRows.Count == 0)
|
||||
throw new InvalidOperationException("Für den gewählten Zeitraum gibt es keine Datensätze in der zentralen Tabelle.");
|
||||
|
||||
var yearlyRows = aggregatedRows;
|
||||
var yearlyRows = scopedRows;
|
||||
|
||||
var dailyBaseRows = selectedRows
|
||||
.Where(r => month.HasValue)
|
||||
@@ -219,7 +222,9 @@ public class ManagementCockpitService : IManagementCockpitService
|
||||
Year = year,
|
||||
Month = month,
|
||||
ValueField = aggregation.ValueField.Key,
|
||||
TargetCurrency = aggregation.TargetCurrency
|
||||
TargetCurrency = aggregation.TargetCurrency,
|
||||
Land = NormalizeOptionalFilter(options?.LandFilter),
|
||||
Tsc = NormalizeOptionalFilter(options?.TscFilter)
|
||||
},
|
||||
Summary = new ManagementCockpitCentralSummary
|
||||
{
|
||||
@@ -239,7 +244,7 @@ public class ManagementCockpitService : IManagementCockpitService
|
||||
AdditionalValueFields = aggregation.AdditionalValueFields
|
||||
.Select(ToValueFieldOption)
|
||||
.ToList(),
|
||||
Notices = BuildCentralNotices(aggregation, selectedRows.Count(x => x.MissingExchangeRate)),
|
||||
Notices = BuildCentralNotices(aggregation, selectedRows.Count(x => x.MissingExchangeRate), options),
|
||||
YearlyTotals = yearlyRows
|
||||
.GroupBy(x => new { x.PeriodDate.Year, x.DisplayCurrency })
|
||||
.OrderBy(g => g.Key.Year)
|
||||
@@ -291,6 +296,18 @@ public class ManagementCockpitService : IManagementCockpitService
|
||||
};
|
||||
}
|
||||
|
||||
private static IEnumerable<CentralAggregationRow> ApplyCentralDimensionFilters(
|
||||
IEnumerable<CentralAggregationRow> rows,
|
||||
ManagementCockpitAnalysisOptions? options)
|
||||
{
|
||||
var landFilter = NormalizeOptionalFilter(options?.LandFilter);
|
||||
var tscFilter = NormalizeOptionalFilter(options?.TscFilter);
|
||||
|
||||
return rows.Where(row =>
|
||||
(landFilter is null || string.Equals(row.Land, landFilter, StringComparison.OrdinalIgnoreCase)) &&
|
||||
(tscFilter is null || string.Equals(row.Tsc, tscFilter, StringComparison.OrdinalIgnoreCase)));
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetCandidateDirectories(ExportSettings settings)
|
||||
{
|
||||
yield return Path.Combine(AppContext.BaseDirectory, "output");
|
||||
@@ -456,7 +473,10 @@ public class ManagementCockpitService : IManagementCockpitService
|
||||
};
|
||||
}
|
||||
|
||||
private static List<string> BuildCentralNotices(AggregationSelection aggregation, int missingExchangeRateCount)
|
||||
private static List<string> BuildCentralNotices(
|
||||
AggregationSelection aggregation,
|
||||
int missingExchangeRateCount,
|
||||
ManagementCockpitAnalysisOptions? options)
|
||||
{
|
||||
var notices = new List<string>
|
||||
{
|
||||
@@ -467,6 +487,13 @@ public class ManagementCockpitService : IManagementCockpitService
|
||||
"Periodenlogik basiert auf Invoice Date, falls vorhanden, sonst auf Extraction Date."
|
||||
};
|
||||
|
||||
var landFilter = NormalizeOptionalFilter(options?.LandFilter);
|
||||
var tscFilter = NormalizeOptionalFilter(options?.TscFilter);
|
||||
if (landFilter is not null || tscFilter is not null)
|
||||
{
|
||||
notices.Add($"Filter aus Auswahl: Land {(landFilter ?? "alle")}, TSC {(tscFilter ?? "alle")}.");
|
||||
}
|
||||
|
||||
if (aggregation.AdditionalValueFields.Count > 0)
|
||||
notices.Add($"Weitere Summenfelder: {string.Join(", ", aggregation.AdditionalValueFields.Select(x => x.Label))}.");
|
||||
|
||||
@@ -488,6 +515,9 @@ public class ManagementCockpitService : IManagementCockpitService
|
||||
return notices;
|
||||
}
|
||||
|
||||
private static string? NormalizeOptionalFilter(string? value)
|
||||
=> string.IsNullOrWhiteSpace(value) ? null : value.Trim();
|
||||
|
||||
private static ManagementCockpitTimeValueRow BuildTimeValueRow(
|
||||
IEnumerable<CentralAggregationRow> groupRows,
|
||||
AggregationSelection aggregation,
|
||||
|
||||
Reference in New Issue
Block a user