Add finance details export and translations

This commit is contained in:
2026-05-21 09:47:59 +02:00
parent b2ede7f8fd
commit 16449f1dc1
11 changed files with 890 additions and 28 deletions
@@ -12,18 +12,30 @@
<MudAppBar Elevation="1" Color="Color.Primary"> <MudAppBar Elevation="1" Color="Color.Primary">
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" <MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start"
OnClick="ToggleDrawer" /> OnClick="ToggleDrawer" />
<MudText Typo="Typo.h6" Class="ml-3 app-title">@T("Trafag Finanze/Sales Management Cockpit", "Trafag Finance/Sales Management Cockpit")</MudText> <MudText Typo="Typo.h6" Class="ml-3 app-title">@T("Trafag Finance/Sales Management Cockpit", "Trafag Finance/Sales Management Cockpit")</MudText>
<MudSpacer /> <MudSpacer />
<MudSelect T="string" <MudMenu Class="mr-3 language-menu"
Value="@UiText.CurrentLanguage" AnchorOrigin="Origin.BottomRight"
ValueChanged="ChangeLanguage" TransformOrigin="Origin.TopRight"
Dense Dense>
Variant="Variant.Outlined" <ActivatorContent>
Class="mr-3" <MudButton Variant="Variant.Outlined"
Style="min-width:100px; color:white;"> Color="Color.Inherit"
<MudSelectItem Value="@("de")">DE</MudSelectItem> Size="Size.Small"
<MudSelectItem Value="@("en")">EN</MudSelectItem> StartIcon="@Icons.Material.Filled.Translate"
</MudSelect> EndIcon="@Icons.Material.Filled.ExpandMore"
Class="language-button">
@LanguageLabel
</MudButton>
</ActivatorContent>
<ChildContent>
<MudMenuItem OnClick="@(() => ChangeLanguage("de"))">Deutsch</MudMenuItem>
<MudMenuItem OnClick="@(() => ChangeLanguage("en"))">English</MudMenuItem>
<MudMenuItem OnClick="@(() => ChangeLanguage("es"))">Español</MudMenuItem>
<MudMenuItem OnClick="@(() => ChangeLanguage("it"))">Italiano</MudMenuItem>
<MudMenuItem OnClick="@(() => ChangeLanguage("hi"))">हिन्दी</MudMenuItem>
</ChildContent>
</MudMenu>
<AuthorizeView> <AuthorizeView>
<Authorized Context="authState"> <Authorized Context="authState">
<MudText Typo="Typo.caption" Class="mr-3">@ShortName(authState.User)</MudText> <MudText Typo="Typo.caption" Class="mr-3">@ShortName(authState.User)</MudText>
@@ -71,6 +83,8 @@
InvokeAsync(StateHasChanged); InvokeAsync(StateHasChanged);
} }
private string LanguageLabel => UiText.CurrentLanguage.ToUpperInvariant();
private string T(string german, string english) => UiText.Text(german, english); private string T(string german, string english) => UiText.Text(german, english);
private static string ShortName(ClaimsPrincipal user) private static string ShortName(ClaimsPrincipal user)
@@ -1,4 +1,5 @@
@using TrafagSalesExporter.Security @using TrafagSalesExporter.Security
@implements IDisposable
@inject TrafagSalesExporter.Services.IUiTextService UiText @inject TrafagSalesExporter.Services.IUiTextService UiText
@inject TrafagSalesExporter.Services.IFinanceCockpitAccessService FinanceAccess @inject TrafagSalesExporter.Services.IFinanceCockpitAccessService FinanceAccess
@inject IConfiguration Configuration @inject IConfiguration Configuration
@@ -58,11 +59,26 @@
@code { @code {
private bool ShowFinanceComparison => Configuration.GetValue("Navigation:ShowFinanceComparison", true); private bool ShowFinanceComparison => Configuration.GetValue("Navigation:ShowFinanceComparison", true);
protected override void OnInitialized()
{
UiText.Changed += HandleLanguageChanged;
}
private void LockFinanceCockpit() private void LockFinanceCockpit()
{ {
FinanceAccess.Lock(); FinanceAccess.Lock();
Navigation.NavigateTo("/"); Navigation.NavigateTo("/");
} }
private void HandleLanguageChanged()
{
InvokeAsync(StateHasChanged);
}
private string T(string german, string english) => UiText.Text(german, english); private string T(string german, string english) => UiText.Text(german, english);
public void Dispose()
{
UiText.Changed -= HandleLanguageChanged;
}
} }
@@ -57,7 +57,8 @@ public class ConsolidatedExportService : IConsolidatedExportService
await _sharePointService.UploadAsync( await _sharePointService.UploadAsync(
spConfig.TenantId, spConfig.ClientId, spConfig.ClientSecret, spConfig.TenantId, spConfig.ClientId, spConfig.ClientSecret,
spConfig.SiteUrl, sharePointFolder, landSubfolder, consolidatedPath); spConfig.SiteUrl, sharePointFolder, landSubfolder, consolidatedPath,
uploadTimestampedCopyIfLocked: true);
} }
return consolidatedPath; return consolidatedPath;
@@ -184,6 +184,7 @@ public class ExcelExportService : IExcelExportService
if (includeFinanceHelpSheet) if (includeFinanceHelpSheet)
{ {
AddFinanceSummarySheet(workbook, records, financeRules); AddFinanceSummarySheet(workbook, records, financeRules);
AddFinanceDetailsSheet(workbook, records, financeRules);
AddFinanceHelpSheet(workbook); AddFinanceHelpSheet(workbook);
} }
@@ -266,6 +267,107 @@ public class ExcelExportService : IExcelExportService
ws.Columns().AdjustToContents(); ws.Columns().AdjustToContents();
} }
private static void AddFinanceDetailsSheet(XLWorkbook workbook, List<SalesRecord> records, IReadOnlyList<FinanceRule> financeRules)
{
var ws = workbook.Worksheets.Add("Finance Details");
var financeRuleEngine = new FinanceRuleEngine(financeRules);
ws.Position = 2;
ws.Cell(1, 1).Value = "Finance Details";
ws.Cell(1, 1).Style.Font.Bold = true;
ws.Cell(1, 1).Style.Font.FontSize = 14;
ws.Cell(2, 1).Value = "Diese Zeilen fuehren zur Summe im Blatt Finance Summary. Summe ueber Net Sales Actual bilden.";
var headers = new[]
{
"Year",
"Country Key",
"Currency",
"Finance Date",
"Net Sales Actual",
"Source Value Field",
"TSC",
"Land",
"Document Type",
"Invoice Number",
"Position on invoice",
"Document Entry",
"Material",
"Name",
"Quantity",
"Customer number",
"Customer name",
"Customer country",
"Supplier number",
"Supplier name",
"Supplier country",
"posting date",
"invoice date",
"Sales Price/Value",
"Sales Currency",
"Document Currency",
"Document Total FC",
"Document Total LC",
"Company Currency"
};
for (var i = 0; i < headers.Length; i++)
{
ws.Cell(4, i + 1).Value = headers[i];
ws.Cell(4, i + 1).Style.Font.Bold = true;
}
var rowIndex = 5;
foreach (var record in records)
{
var countryKey = ResolveFinanceCountryKey(record.Land, record.Tsc);
var financeDate = financeRuleEngine.ResolveFinanceDate(record, countryKey);
var rawInclude = financeRuleEngine.ShouldInclude(record, countryKey);
var netSalesActual = financeRuleEngine.ResolveNetSalesActual(record, countryKey, rawInclude);
var include = rawInclude && netSalesActual != 0m;
if (!include)
continue;
ws.Cell(rowIndex, 1).Value = financeDate.Year;
ws.Cell(rowIndex, 2).Value = countryKey;
ws.Cell(rowIndex, 3).Value = ResolveFinanceCurrency(record);
ws.Cell(rowIndex, 4).Value = financeDate.ToString("dd.MM.yyyy");
ws.Cell(rowIndex, 5).Value = netSalesActual;
ws.Cell(rowIndex, 6).Value = "Sales Price/Value";
ws.Cell(rowIndex, 7).Value = record.Tsc;
ws.Cell(rowIndex, 8).Value = record.Land;
ws.Cell(rowIndex, 9).Value = record.DocumentType;
ws.Cell(rowIndex, 10).Value = record.InvoiceNumber;
ws.Cell(rowIndex, 11).Value = record.PositionOnInvoice;
ws.Cell(rowIndex, 12).Value = record.DocumentEntry;
ws.Cell(rowIndex, 13).Value = record.Material;
ws.Cell(rowIndex, 14).Value = record.Name;
ws.Cell(rowIndex, 15).Value = record.Quantity;
ws.Cell(rowIndex, 16).Value = record.CustomerNumber;
ws.Cell(rowIndex, 17).Value = record.CustomerName;
ws.Cell(rowIndex, 18).Value = record.CustomerCountry;
ws.Cell(rowIndex, 19).Value = record.SupplierNumber;
ws.Cell(rowIndex, 20).Value = record.SupplierName;
ws.Cell(rowIndex, 21).Value = record.SupplierCountry;
ws.Cell(rowIndex, 22).Value = record.PostingDate?.ToString("dd.MM.yyyy") ?? string.Empty;
ws.Cell(rowIndex, 23).Value = record.InvoiceDate?.ToString("dd.MM.yyyy") ?? string.Empty;
ws.Cell(rowIndex, 24).Value = record.SalesPriceValue;
ws.Cell(rowIndex, 25).Value = record.SalesCurrency;
ws.Cell(rowIndex, 26).Value = record.DocumentCurrency;
ws.Cell(rowIndex, 27).Value = record.DocumentTotalForeignCurrency;
ws.Cell(rowIndex, 28).Value = record.DocumentTotalLocalCurrency;
ws.Cell(rowIndex, 29).Value = record.CompanyCurrency;
rowIndex++;
}
ws.Column(5).Style.NumberFormat.Format = "#,##0.00";
ws.Column(24).Style.NumberFormat.Format = "#,##0.00";
ws.Column(27).Style.NumberFormat.Format = "#,##0.00";
ws.Column(28).Style.NumberFormat.Format = "#,##0.00";
ws.Columns().AdjustToContents();
}
private static string BuildFinanceSummaryHint(string countryKey) private static string BuildFinanceSummaryHint(string countryKey)
=> countryKey.ToUpperInvariant() switch => countryKey.ToUpperInvariant() switch
{ {
@@ -290,6 +392,7 @@ public class ExcelExportService : IExcelExportService
("2. Land filtern", "Finance | Country Key = CH, AT, DE, ES, FR, IN, IT, UK oder US"), ("2. Land filtern", "Finance | Country Key = CH, AT, DE, ES, FR, IN, IT, UK oder US"),
("3. Gueltige Zeilen filtern", "Finance | Include = TRUE"), ("3. Gueltige Zeilen filtern", "Finance | Include = TRUE"),
("4. Summe bilden", "Finance | Net Sales Actual summieren"), ("4. Summe bilden", "Finance | Net Sales Actual summieren"),
("Detailblatt", "Finance Details enthaelt nur die Zeilen, die zur Summe im Blatt Finance Summary fuehren."),
("Waehrung", "Finance | Currency zeigt die fuer den Finance-Abgleich fuehrende Hauswaehrung."), ("Waehrung", "Finance | Currency zeigt die fuer den Finance-Abgleich fuehrende Hauswaehrung."),
("Datum", "Finance | Date verwendet PostingDate, danach InvoiceDate, danach ExtractionDate. DE Alphaplan wird als Jahresfile 2025 behandelt."), ("Datum", "Finance | Date verwendet PostingDate, danach InvoiceDate, danach ExtractionDate. DE Alphaplan wird als Jahresfile 2025 behandelt."),
("Wertquelle", "Finance | Source Value Field zeigt, aus welchem Rohfeld der Finance-Wert kommt."), ("Wertquelle", "Finance | Source Value Field zeigt, aus welchem Rohfeld der Finance-Wert kommt."),
@@ -2,7 +2,7 @@ namespace TrafagSalesExporter.Services;
public interface ISharePointUploadService public interface ISharePointUploadService
{ {
Task UploadAsync(string tenantId, string clientId, string clientSecret, string siteUrl, string exportFolder, string land, string localFilePath); Task UploadAsync(string tenantId, string clientId, string clientSecret, string siteUrl, string exportFolder, string land, string localFilePath, bool uploadTimestampedCopyIfLocked = false);
Task<string> DownloadToTempFileAsync(string tenantId, string clientId, string clientSecret, string siteUrl, string fileReference); Task<string> DownloadToTempFileAsync(string tenantId, string clientId, string clientSecret, string siteUrl, string fileReference);
Task<SharePointFileReference> ResolveLatestFileInFolderAsync(string tenantId, string clientId, string clientSecret, string siteUrl, string folderReference, string siteTsc, int? preferredYear = null); Task<SharePointFileReference> ResolveLatestFileInFolderAsync(string tenantId, string clientId, string clientSecret, string siteUrl, string folderReference, string siteTsc, int? preferredYear = null);
Task<IReadOnlyList<SharePointFileReference>> ResolveManualImportFilesInFolderAsync(string tenantId, string clientId, string clientSecret, string siteUrl, string folderReference, string siteTsc, int? preferredYear = null); Task<IReadOnlyList<SharePointFileReference>> ResolveManualImportFilesInFolderAsync(string tenantId, string clientId, string clientSecret, string siteUrl, string folderReference, string siteTsc, int? preferredYear = null);
@@ -10,7 +10,7 @@ namespace TrafagSalesExporter.Services;
public class SharePointUploadService : ISharePointUploadService public class SharePointUploadService : ISharePointUploadService
{ {
public async Task UploadAsync(string tenantId, string clientId, string clientSecret, public async Task UploadAsync(string tenantId, string clientId, string clientSecret,
string siteUrl, string exportFolder, string land, string localFilePath) string siteUrl, string exportFolder, string land, string localFilePath, bool uploadTimestampedCopyIfLocked = false)
{ {
var normalizedTenantId = Normalize(tenantId); var normalizedTenantId = Normalize(tenantId);
var normalizedClientId = Normalize(clientId); var normalizedClientId = Normalize(clientId);
@@ -33,17 +33,16 @@ public class SharePointUploadService : ISharePointUploadService
if (drive?.Id is null) if (drive?.Id is null)
throw new InvalidOperationException("SharePoint Dokumentenbibliothek konnte nicht gefunden werden."); throw new InvalidOperationException("SharePoint Dokumentenbibliothek konnte nicht gefunden werden.");
var fileName = Path.GetFileName(localFilePath); var remotePath = BuildUploadPath(normalizedExportFolder, normalizedLand, Path.GetFileName(localFilePath));
var remotePath = string.Join("/", try
new[]
{ {
normalizedExportFolder.Trim('/').Trim(), await UploadWithLockRetryAsync(graphClient, drive.Id, remotePath, localFilePath);
normalizedLand.Trim('/').Trim(), }
fileName catch (Microsoft.Graph.Models.ODataErrors.ODataError ex) when (uploadTimestampedCopyIfLocked && IsLockedSharePointResource(ex))
}.Where(segment => !string.IsNullOrWhiteSpace(segment))); {
var timestampedPath = BuildUploadPath(normalizedExportFolder, normalizedLand, BuildTimestampedFileName(localFilePath));
await using var stream = File.OpenRead(localFilePath); await UploadWithLockRetryAsync(graphClient, drive.Id, timestampedPath, localFilePath);
await graphClient.Drives[drive.Id].Root.ItemWithPath(remotePath).Content.PutAsync(stream); }
} }
public async Task<string> DownloadToTempFileAsync(string tenantId, string clientId, string clientSecret, string siteUrl, string fileReference) public async Task<string> DownloadToTempFileAsync(string tenantId, string clientId, string clientSecret, string siteUrl, string fileReference)
@@ -242,6 +241,45 @@ public class SharePointUploadService : ISharePointUploadService
private static string Normalize(string value) => value?.Trim() ?? string.Empty; private static string Normalize(string value) => value?.Trim() ?? string.Empty;
private static async Task UploadWithLockRetryAsync(GraphServiceClient graphClient, string driveId, string remotePath, string localFilePath)
{
const int attempts = 4;
for (var attempt = 1; attempt <= attempts; attempt++)
{
try
{
await using var stream = File.OpenRead(localFilePath);
await graphClient.Drives[driveId].Root.ItemWithPath(remotePath).Content.PutAsync(stream);
return;
}
catch (Microsoft.Graph.Models.ODataErrors.ODataError ex) when (attempt < attempts && IsLockedSharePointResource(ex))
{
await Task.Delay(TimeSpan.FromSeconds(5 * attempt));
}
}
}
private static string BuildUploadPath(string exportFolder, string land, string fileName)
=> string.Join("/",
new[]
{
exportFolder.Trim('/').Trim(),
land.Trim('/').Trim(),
fileName
}.Where(segment => !string.IsNullOrWhiteSpace(segment)));
private static string BuildTimestampedFileName(string localFilePath)
{
var name = Path.GetFileNameWithoutExtension(localFilePath);
var extension = Path.GetExtension(localFilePath);
var timestamp = DateTime.Now.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture);
return $"{name}_{timestamp}{extension}";
}
private static bool IsLockedSharePointResource(Exception ex)
=> ex.Message.Contains("locked", StringComparison.OrdinalIgnoreCase) ||
ex.ToString().Contains("locked", StringComparison.OrdinalIgnoreCase);
private static string ResolveRemotePath(string fileReference, Uri siteUri) private static string ResolveRemotePath(string fileReference, Uri siteUri)
{ {
if (Uri.TryCreate(fileReference, UriKind.Absolute, out var fileUri)) if (Uri.TryCreate(fileReference, UriKind.Absolute, out var fileUri))
+636 -2
View File
@@ -11,6 +11,622 @@ public interface IUiTextService
public sealed class UiTextService : IUiTextService public sealed class UiTextService : IUiTextService
{ {
private string _currentLanguage = "de"; private string _currentLanguage = "de";
private static readonly HashSet<string> SupportedLanguages = new(StringComparer.OrdinalIgnoreCase)
{
"de",
"en",
"es",
"it",
"hi"
};
private static readonly IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> Translations =
new Dictionary<string, IReadOnlyDictionary<string, string>>(StringComparer.OrdinalIgnoreCase)
{
["es"] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
["Trafag Finance/Sales Management Cockpit"] = "Trafag Cockpit de finanzas y ventas",
["Finance Cockpit"] = "Cockpit financiero",
["Finance Cockpit ist geschuetzt. Bitte separat anmelden."] = "El cockpit financiero está protegido. Inicie sesión por separado.",
["Finance-Cockpit-Zugang ist noch nicht konfiguriert. Bitte Username und PasswordHash in FinanceCockpitAccess konfigurieren."] = "El acceso al cockpit financiero aún no está configurado. Configure Username y PasswordHash en FinanceCockpitAccess.",
["Export Dashboard"] = "Panel de exportación",
["Management Analyse"] = "Análisis de gestión",
["Soll/Ist Vergleich"] = "Comparación real/referencia",
["Manuelle Importe"] = "Importaciones manuales",
["Admin"] = "Administración",
["Standorte"] = "Sitios",
["Transformationen"] = "Transformaciones",
["Finance Regeln"] = "Reglas financieras",
["Settings"] = "Configuración",
["Logs"] = "Registros",
["Finance sperren"] = "Bloquear finanzas",
["HR KPI (Login)"] = "KPI RR. HH. (login)",
["HR KPI enthaelt sensible Personaldaten. Bitte separat anmelden."] = "HR KPI contiene datos personales sensibles. Inicie sesión por separado.",
["HR-KPI-Zugang ist noch nicht konfiguriert. Bitte Username und PasswordHash in HrKpiAccess konfigurieren."] = "El acceso a HR KPI aún no está configurado. Configure Username y PasswordHash en HrKpiAccess.",
["HR KPI entsperren"] = "Desbloquear HR KPI",
["HR-KPI-Anmeldung fehlgeschlagen."] = "Error al iniciar sesión en HR KPI.",
["Name"] = "Nombre",
["Passwort"] = "Contraseña",
["Finance Cockpit entsperren"] = "Desbloquear cockpit financiero",
["Finance-Jahr"] = "Año financiero",
["Finance Summary laden"] = "Cargar resumen financiero",
["Finance Summary"] = "Resumen financiero",
["Net Sales Actual"] = "Ventas netas reales",
["gefiltertes Endergebnis"] = "resultado final filtrado",
["Enthaltene Zeilen"] = "Filas incluidas",
["Finance Include = TRUE"] = "Finance Include = TRUE",
["Finance-Regeln"] = "Reglas financieras",
["Laender / Waehrungen"] = "Países / monedas",
["Summen wie im Excel-Blatt Finance Summary"] = "Totales como en la hoja Excel Finance Summary",
["Keine Finance-Summary-Daten fuer diese Filter."] = "No hay datos de resumen financiero para estos filtros.",
["Jahresvergleich mit aktuellem Filter"] = "Comparación anual con el filtro actual",
["Rohdaten Diagnose"] = "Diagnóstico de datos brutos",
["Diese Analyse ist eine Plausibilitaets- und Rohdatensicht. Fuer den verbindlichen Finance-Abgleich bitte `Soll/Ist Vergleich` oder im Endexcel die `Finance | ...`-Spalten verwenden."] = "Este análisis es una vista de plausibilidad y datos brutos. Para la reconciliación financiera vinculante, use `Comparación real/referencia` o las columnas `Finance | ...` en el Excel final.",
["Diese Regeln wirken nur auf die Finance-Sicht im zentralen Excel und im Abgleich. Rohdaten und Spaltenmapping bleiben unveraendert."] = "Estas reglas solo afectan la vista financiera en el Excel consolidado y en la reconciliación. Los datos brutos y el mapeo de columnas no cambian.",
["Finance-Cockpit-Anmeldung fehlgeschlagen."] = "Error al iniciar sesión en el cockpit financiero.",
["Alle exportieren"] = "Exportar todo",
["Zentrale Datei neu erzeugen"] = "Reconstruir archivo consolidado",
["Aktualisieren"] = "Actualizar",
["Lade..."] = "Cargando...",
["Speichern"] = "Guardar",
["Schliessen"] = "Cerrar",
["Regel hinzufuegen"] = "Añadir regla",
["Alle speichern"] = "Guardar todo",
["Code anzeigen"] = "Mostrar código",
["Land"] = "País",
["Laender"] = "Países",
["Aktiv"] = "Activo",
["Aktionen"] = "Acciones",
["Status"] = "Estado",
["Basis"] = "Base",
["Quelle"] = "Fuente",
["Schema"] = "Esquema",
["Server"] = "Servidor",
["Zeilen"] = "Filas",
["Rechnungen"] = "Facturas",
["Waehrung"] = "Moneda",
["Differenz"] = "Diferencia",
["Berechnung"] = "Cálculo",
["Varianten"] = "Variantes",
["Referenz"] = "Referencia",
["Ist 2025"] = "Real 2025",
["Ampel"] = "Estado",
["Ohne Ist"] = "Sin valores reales vacíos",
["Varianten anzeigen"] = "mostrar variantes",
["Abgrenzung"] = "Alcance",
["Wert"] = "Valor",
["Jahr"] = "Año",
["Monat"] = "Mes",
["Tag"] = "Día",
["Hinweise"] = "Notas",
["Enthalten"] = "Incluido",
["Ausgeschlossen"] = "Excluido",
["Gefiltert"] = "Filtrado",
["Global"] = "Global",
["Top Kunden"] = "Clientes principales",
["Top Produktgruppen"] = "Grupos de productos principales",
["Datenqualitaet"] = "Calidad de datos",
["Management Aussagen"] = "Declaraciones de gestión",
["Nicht umgerechnet"] = "No convertido",
["Jahreswerte"] = "Valores anuales",
["Monatswerte"] = "Valores mensuales",
["Werte nach Quelle"] = "Valores por fuente",
["Werte nach Land"] = "Valores por país",
["Rohdaten Diagnose"] = "Diagnóstico de datos brutos",
["Vorhandene Excel-Datei"] = "Archivo Excel disponible",
["Summenfeld"] = "Campo de valor",
["Anzeige-Waehrung"] = "Moneda de visualización",
["Dateien laden"] = "Cargar archivos",
["Cockpit erzeugen"] = "Crear cockpit",
["Zentrale Auswertung laden"] = "Cargar análisis central",
["Datei / SharePoint-Ordner"] = "Archivo / carpeta SharePoint",
["Letzter Upload"] = "Última carga",
["Pfad pruefen"] = "Comprobar ruta",
["Importdateien"] = "Archivos de importación",
["Anleitung"] = "Guía",
["Excel bereitstellen"] = "Preparar Excel",
["Speichern und aktivieren"] = "Guardar y activar",
["Standort exportieren"] = "Exportar sitio",
["Zentrale Excel erzeugen"] = "Crear Excel consolidado",
["Finance pruefen"] = "Comprobar finanzas",
["Datei hochladen oder SharePoint-/UNC-Pfad eintragen."] = "Cargue un archivo o introduzca una ruta de SharePoint/UNC.",
["Pfad pruefen, Standort aktiv setzen und speichern."] = "Compruebe la ruta, active el sitio y guarde.",
["Im Export Dashboard den Standort starten. Die Daten landen in CentralSalesRecords."] = "Inicie el sitio en el panel de exportación. Los datos se escriben en CentralSalesRecords.",
["Danach `Zentrale Datei neu erzeugen` ausfuehren."] = "Después ejecute `Reconstruir archivo consolidado`.",
["Im Endexcel `Finance | ...` oder im Reiter `Soll/Ist Vergleich` kontrollieren."] = "Compruebe las columnas `Finance | ...` en el Excel final o la pestaña `Comparación real/referencia`.",
["Richtige Reihenfolge"] = "Orden correcto",
["Ein Standortexport aktualisiert die Datenbasis. Die zentrale Excel muss danach neu erzeugt werden."] = "Una exportación de sitio actualiza la base de datos. Después debe reconstruirse el Excel consolidado.",
["DE bleibt fachlich offen"] = "DE sigue pendiente a nivel funcional",
["Alphaplan ist technisch importierbar. Kundenlaender und Filter fuer den offiziellen DE-Istwert muessen noch bestaetigt werden."] = "Alphaplan puede importarse técnicamente. Los países de cliente y filtros para el valor real oficial de DE aún deben confirmarse.",
["Server-Hinweis"] = "Nota del servidor",
["Der Server braucht kein Microsoft Excel. XLSX/CSV wird direkt von der Anwendung gelesen."] = "El servidor no necesita Microsoft Excel. La aplicación lee XLSX/CSV directamente.",
["Datei hochgeladen."] = "Archivo cargado.",
["Upload fehlgeschlagen"] = "Error de carga",
["Speichern fehlgeschlagen"] = "Error al guardar",
["Ueberblick"] = "Resumen",
["Fluktuation"] = "Rotación",
["Austritte"] = "Salidas",
["Austritte nach Austrittsart"] = "Salidas por tipo",
["Austritte nach Organisation"] = "Salidas por organización",
["Absenzen"] = "Ausencias",
["Absenzen nach Organisation"] = "Ausencias por organización",
["Krankheitstage"] = "Días de enfermedad",
["Absenzen je Mitarbeiter"] = "Ausencias por empleado",
["Personalnr."] = "N.º personal",
["Organisation"] = "Organización",
["Kurz"] = "Corto",
["Lang"] = "Largo",
["Gesamt"] = "Total",
["Quote"] = "Tasa",
["Zeit / Ferien"] = "Tiempo / vacaciones",
["Kritische Restferien"] = "Vacaciones restantes críticas",
["Rest"] = "Restante",
["Ausstehend"] = "Pendiente",
["Mitarbeitende"] = "Empleados",
["Datenstatus"] = "Estado de datos",
["Headcount nach Organisation"] = "Plantilla por organización",
["HR-Ampel"] = "Estado RR. HH.",
["Schwere"] = "Gravedad",
["Bereich"] = "Área",
["Pruefpunkt"] = "Comprobación",
["Anzahl"] = "Cantidad",
["Hinweis"] = "Nota",
["Gruppe"] = "Grupo",
["Hoechste Absenzen"] = "Ausencias más altas",
["Kritische GLZ-Saldi"] = "Saldos horarios críticos",
["Saldo"] = "Saldo",
["Austritt"] = "Salida",
["Austrittsart"] = "Tipo de salida",
["Ausschlussgruende"] = "Motivos de exclusión",
["Grund"] = "Motivo",
["Kostenstelle"] = "Centro de coste",
["Alter"] = "Edad",
["Dienstjahre"] = "Años de servicio",
["Typ"] = "Tipo",
["Dateistatus"] = "Estado de archivos",
["Stand"] = "Modificado",
["Rexx exportieren"] = "Exportar desde Rexx",
["Die benoetigten Rexx-Abfragen manuell herunterladen. Excel/XLSX verwenden, nicht PDF."] = "Descargue manualmente las consultas Rexx necesarias. Use Excel/XLSX, no PDF.",
["Dateien ablegen"] = "Guardar archivos",
["Downloads in den Datenordner kopieren und exakt wie unten benennen."] = "Copie las descargas en la carpeta de datos y nómbrelas exactamente como se indica abajo.",
["Cockpit laden"] = "Cargar cockpit",
["Im HR-KPI-Cockpit den Datenordner kontrollieren und Laden klicken."] = "En el cockpit HR KPI, compruebe la carpeta de datos y haga clic en Cargar.",
["Datenstatus pruefen"] = "Comprobar estado de datos",
["Im Reiter Datenstatus muessen die erwarteten Dateien gruen erscheinen."] = "En la pestaña Estado de datos, los archivos esperados deben aparecer en verde.",
["Datenordner"] = "Carpeta de datos",
["Der Standardordner ist konfigurierbar. Fuer einen anderen Ordner oben im HR-KPI-Filter den Datenordner anpassen und neu laden."] = "La carpeta predeterminada es configurable. Para usar otra carpeta, cambie la carpeta de datos en el filtro HR KPI superior y vuelva a cargar.",
["HR-Dateien enthalten Personendaten. Nicht per E-Mail weiterleiten und keine Kopien in ungeschuetzten Ordnern liegen lassen."] = "Los archivos de HR contienen datos personales. No los reenvíe por correo electrónico ni deje copias en carpetas no protegidas.",
["Neue Auswertungen im Cockpit"] = "Nuevas vistas en el cockpit",
["Managementsicht anonymisiert Personendaten fuer Fuehrungsberichte."] = "La vista de gestión anonimiza los datos personales para informes directivos.",
["Dateistatus zeigt Pfad, Zeilen, Aenderungsdatum, Alter und Frische."] = "El estado de archivos muestra ruta, filas, fecha de modificación, antigüedad y frescura.",
["HR-Ampel fasst Fluktuation, Krankheit, GLZ, Restferien und Datenqualitaet zusammen."] = "El estado HR resume rotación, enfermedad, saldo horario, vacaciones restantes y calidad de datos.",
["GLZ- und Restferien-Ampeln koennen gefiltert werden."] = "Los estados de saldo horario y vacaciones restantes pueden filtrarse.",
["Periodenvergleich zeigt die wichtigsten Vorjahreswerte, soweit Daten vorhanden sind."] = "La comparación de periodos muestra los valores clave del año anterior cuando hay datos disponibles.",
["Datenqualitaet markiert fehlende Dateien, alte Dateien und auffaellige Werte."] = "La calidad de datos marca archivos faltantes, archivos antiguos y valores llamativos.",
["Austritte werden nach Austrittsart und Organisation gruppiert."] = "Las salidas se agrupan por tipo de salida y organización.",
["Absenzen werden nach Organisation ausgewertet."] = "Las ausencias se evalúan por organización.",
["Top-Absenzen und kritische Detailtabellen helfen bei der operativen Pruefung."] = "Las ausencias principales y tablas críticas ayudan en la revisión operativa.",
["Drucken/PDF erzeugt eine weitergebbare Ansicht aus dem Browser."] = "Imprimir/PDF crea una vista compartible desde el navegador.",
["Erwartete Dateien"] = "Archivos esperados",
["Inhalt"] = "Contenido",
["Datei/Pfad"] = "Archivo/ruta",
["gefunden"] = "encontrado",
["fehlt"] = "falta",
["Source Viewer"] = "Visor de código",
["Zurueck zur Transformation"] = "Volver a transformaciones",
["Datei:"] = "Archivo:",
["Klasse:"] = "Clase:",
["bei Zeile"] = "en línea",
["Kein Dateipfad angegeben."] = "No se indicó ninguna ruta de archivo.",
["Ungueltiger Dateipfad."] = "Ruta de archivo no válida.",
["Transformer Ansicht"] = "Vista de transformaciones",
["Transformationscode"] = "Código de transformación",
["Keine Beschreibung."] = "Sin descripción.",
["Optionales Argument."] = "Argumento opcional."
},
["it"] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
["Trafag Finance/Sales Management Cockpit"] = "Cockpit Trafag finanza e vendite",
["Finance Cockpit"] = "Cockpit finance",
["Finance Cockpit ist geschuetzt. Bitte separat anmelden."] = "Il cockpit finance è protetto. Effettuare un accesso separato.",
["Finance-Cockpit-Zugang ist noch nicht konfiguriert. Bitte Username und PasswordHash in FinanceCockpitAccess konfigurieren."] = "L'accesso al cockpit finance non è ancora configurato. Configurare Username e PasswordHash in FinanceCockpitAccess.",
["Export Dashboard"] = "Dashboard esportazioni",
["Management Analyse"] = "Analisi di gestione",
["Soll/Ist Vergleich"] = "Confronto consuntivo/riferimento",
["Manuelle Importe"] = "Import manuali",
["Admin"] = "Amministrazione",
["Standorte"] = "Sedi",
["Transformationen"] = "Trasformazioni",
["Finance Regeln"] = "Regole finance",
["Settings"] = "Impostazioni",
["Logs"] = "Log",
["Finance sperren"] = "Blocca finance",
["HR KPI (Login)"] = "KPI HR (login)",
["HR KPI enthaelt sensible Personaldaten. Bitte separat anmelden."] = "HR KPI contiene dati personali sensibili. Effettuare un accesso separato.",
["HR-KPI-Zugang ist noch nicht konfiguriert. Bitte Username und PasswordHash in HrKpiAccess konfigurieren."] = "L'accesso a HR KPI non è ancora configurato. Configurare Username e PasswordHash in HrKpiAccess.",
["HR KPI entsperren"] = "Sblocca HR KPI",
["HR-KPI-Anmeldung fehlgeschlagen."] = "Accesso a HR KPI non riuscito.",
["Name"] = "Nome",
["Passwort"] = "Password",
["Finance Cockpit entsperren"] = "Sblocca cockpit finance",
["Finance-Jahr"] = "Anno finance",
["Finance Summary laden"] = "Carica riepilogo finance",
["Finance Summary"] = "Riepilogo finance",
["Net Sales Actual"] = "Vendite nette consuntive",
["gefiltertes Endergebnis"] = "risultato finale filtrato",
["Enthaltene Zeilen"] = "Righe incluse",
["Finance Include = TRUE"] = "Finance Include = TRUE",
["Finance-Regeln"] = "Regole finance",
["Laender / Waehrungen"] = "Paesi / valute",
["Summen wie im Excel-Blatt Finance Summary"] = "Totali come nel foglio Excel Finance Summary",
["Keine Finance-Summary-Daten fuer diese Filter."] = "Nessun dato di riepilogo finance per questi filtri.",
["Jahresvergleich mit aktuellem Filter"] = "Confronto annuale con filtro attuale",
["Rohdaten Diagnose"] = "Diagnosi dati grezzi",
["Diese Analyse ist eine Plausibilitaets- und Rohdatensicht. Fuer den verbindlichen Finance-Abgleich bitte `Soll/Ist Vergleich` oder im Endexcel die `Finance | ...`-Spalten verwenden."] = "Questa analisi è una vista di plausibilità e dati grezzi. Per la riconciliazione finance vincolante usare `Confronto consuntivo/riferimento` o le colonne `Finance | ...` nell'Excel finale.",
["Diese Regeln wirken nur auf die Finance-Sicht im zentralen Excel und im Abgleich. Rohdaten und Spaltenmapping bleiben unveraendert."] = "Queste regole agiscono solo sulla vista finance nell'Excel consolidato e nella riconciliazione. Dati grezzi e mapping colonne restano invariati.",
["Finance-Cockpit-Anmeldung fehlgeschlagen."] = "Accesso al cockpit finance non riuscito.",
["Alle exportieren"] = "Esporta tutto",
["Zentrale Datei neu erzeugen"] = "Ricrea file consolidato",
["Aktualisieren"] = "Aggiorna",
["Lade..."] = "Caricamento...",
["Speichern"] = "Salva",
["Schliessen"] = "Chiudi",
["Regel hinzufuegen"] = "Aggiungi regola",
["Alle speichern"] = "Salva tutto",
["Code anzeigen"] = "Mostra codice",
["Land"] = "Paese",
["Laender"] = "Paesi",
["Aktiv"] = "Attivo",
["Aktionen"] = "Azioni",
["Status"] = "Stato",
["Basis"] = "Base",
["Quelle"] = "Fonte",
["Schema"] = "Schema",
["Server"] = "Server",
["Zeilen"] = "Righe",
["Rechnungen"] = "Fatture",
["Waehrung"] = "Valuta",
["Differenz"] = "Differenza",
["Berechnung"] = "Calcolo",
["Varianten"] = "Varianti",
["Referenz"] = "Riferimento",
["Ist 2025"] = "Consuntivo 2025",
["Ampel"] = "Stato",
["Ohne Ist"] = "Senza consuntivi vuoti",
["Varianten anzeigen"] = "mostra varianti",
["Abgrenzung"] = "Ambito",
["Wert"] = "Valore",
["Jahr"] = "Anno",
["Monat"] = "Mese",
["Tag"] = "Giorno",
["Hinweise"] = "Note",
["Enthalten"] = "Incluso",
["Ausgeschlossen"] = "Escluso",
["Gefiltert"] = "Filtrato",
["Global"] = "Globale",
["Top Kunden"] = "Clienti principali",
["Top Produktgruppen"] = "Gruppi prodotto principali",
["Datenqualitaet"] = "Qualità dati",
["Management Aussagen"] = "Indicazioni management",
["Nicht umgerechnet"] = "Non convertito",
["Jahreswerte"] = "Valori annuali",
["Monatswerte"] = "Valori mensili",
["Werte nach Quelle"] = "Valori per fonte",
["Werte nach Land"] = "Valori per paese",
["Rohdaten Diagnose"] = "Diagnosi dati grezzi",
["Vorhandene Excel-Datei"] = "File Excel disponibile",
["Summenfeld"] = "Campo valore",
["Anzeige-Waehrung"] = "Valuta visualizzata",
["Dateien laden"] = "Carica file",
["Cockpit erzeugen"] = "Crea cockpit",
["Zentrale Auswertung laden"] = "Carica analisi centrale",
["Datei / SharePoint-Ordner"] = "File / cartella SharePoint",
["Letzter Upload"] = "Ultimo upload",
["Pfad pruefen"] = "Controlla percorso",
["Importdateien"] = "File di importazione",
["Anleitung"] = "Guida",
["Excel bereitstellen"] = "Prepara Excel",
["Speichern und aktivieren"] = "Salva e attiva",
["Standort exportieren"] = "Esporta sede",
["Zentrale Excel erzeugen"] = "Crea Excel consolidato",
["Finance pruefen"] = "Controlla finance",
["Datei hochladen oder SharePoint-/UNC-Pfad eintragen."] = "Caricare un file o inserire un percorso SharePoint/UNC.",
["Pfad pruefen, Standort aktiv setzen und speichern."] = "Controllare il percorso, attivare la sede e salvare.",
["Im Export Dashboard den Standort starten. Die Daten landen in CentralSalesRecords."] = "Avviare la sede nel dashboard esportazioni. I dati vengono scritti in CentralSalesRecords.",
["Danach `Zentrale Datei neu erzeugen` ausfuehren."] = "Poi eseguire `Ricrea file consolidato`.",
["Im Endexcel `Finance | ...` oder im Reiter `Soll/Ist Vergleich` kontrollieren."] = "Controllare le colonne `Finance | ...` nell'Excel finale o la scheda `Confronto consuntivo/riferimento`.",
["Richtige Reihenfolge"] = "Sequenza corretta",
["Ein Standortexport aktualisiert die Datenbasis. Die zentrale Excel muss danach neu erzeugt werden."] = "L'esportazione di una sede aggiorna la base dati. L'Excel consolidato deve poi essere ricreato.",
["DE bleibt fachlich offen"] = "DE resta aperta sul piano funzionale",
["Alphaplan ist technisch importierbar. Kundenlaender und Filter fuer den offiziellen DE-Istwert muessen noch bestaetigt werden."] = "Alphaplan è tecnicamente importabile. Paesi cliente e filtri per il valore consuntivo ufficiale DE devono ancora essere confermati.",
["Server-Hinweis"] = "Nota server",
["Der Server braucht kein Microsoft Excel. XLSX/CSV wird direkt von der Anwendung gelesen."] = "Il server non richiede Microsoft Excel. XLSX/CSV viene letto direttamente dall'applicazione.",
["Datei hochgeladen."] = "File caricato.",
["Upload fehlgeschlagen"] = "Upload non riuscito",
["Speichern fehlgeschlagen"] = "Salvataggio non riuscito",
["Ueberblick"] = "Panoramica",
["Fluktuation"] = "Turnover",
["Austritte"] = "Uscite",
["Austritte nach Austrittsart"] = "Uscite per tipo",
["Austritte nach Organisation"] = "Uscite per organizzazione",
["Absenzen"] = "Assenze",
["Absenzen nach Organisation"] = "Assenze per organizzazione",
["Krankheitstage"] = "Giorni di malattia",
["Absenzen je Mitarbeiter"] = "Assenze per collaboratore",
["Personalnr."] = "N. personale",
["Organisation"] = "Organizzazione",
["Kurz"] = "Breve",
["Lang"] = "Lungo",
["Gesamt"] = "Totale",
["Quote"] = "Tasso",
["Zeit / Ferien"] = "Tempo / ferie",
["Kritische Restferien"] = "Ferie residue critiche",
["Rest"] = "Residuo",
["Ausstehend"] = "Aperto",
["Mitarbeitende"] = "Collaboratori",
["Datenstatus"] = "Stato dati",
["Headcount nach Organisation"] = "Organico per organizzazione",
["HR-Ampel"] = "Stato HR",
["Schwere"] = "Gravità",
["Bereich"] = "Area",
["Pruefpunkt"] = "Controllo",
["Anzahl"] = "Numero",
["Hinweis"] = "Nota",
["Gruppe"] = "Gruppo",
["Hoechste Absenzen"] = "Assenze più alte",
["Kritische GLZ-Saldi"] = "Saldi orari critici",
["Saldo"] = "Saldo",
["Austritt"] = "Uscita",
["Austrittsart"] = "Tipo di uscita",
["Ausschlussgruende"] = "Motivi di esclusione",
["Grund"] = "Motivo",
["Kostenstelle"] = "Centro di costo",
["Alter"] = "Età",
["Dienstjahre"] = "Anni di servizio",
["Typ"] = "Tipo",
["Dateistatus"] = "Stato file",
["Stand"] = "Modificato",
["Rexx exportieren"] = "Esporta da Rexx",
["Die benoetigten Rexx-Abfragen manuell herunterladen. Excel/XLSX verwenden, nicht PDF."] = "Scaricare manualmente le query Rexx necessarie. Usare Excel/XLSX, non PDF.",
["Dateien ablegen"] = "Archivia file",
["Downloads in den Datenordner kopieren und exakt wie unten benennen."] = "Copiare i download nella cartella dati e denominarli esattamente come indicato sotto.",
["Cockpit laden"] = "Carica cockpit",
["Im HR-KPI-Cockpit den Datenordner kontrollieren und Laden klicken."] = "Nel cockpit HR KPI controllare la cartella dati e fare clic su Carica.",
["Datenstatus pruefen"] = "Controlla stato dati",
["Im Reiter Datenstatus muessen die erwarteten Dateien gruen erscheinen."] = "Nella scheda Stato dati i file attesi devono apparire in verde.",
["Datenordner"] = "Cartella dati",
["Der Standardordner ist konfigurierbar. Fuer einen anderen Ordner oben im HR-KPI-Filter den Datenordner anpassen und neu laden."] = "La cartella predefinita è configurabile. Per usare un'altra cartella, modificare la cartella dati nel filtro HR KPI in alto e ricaricare.",
["HR-Dateien enthalten Personendaten. Nicht per E-Mail weiterleiten und keine Kopien in ungeschuetzten Ordnern liegen lassen."] = "I file HR contengono dati personali. Non inoltrarli via e-mail e non lasciare copie in cartelle non protette.",
["Neue Auswertungen im Cockpit"] = "Nuove viste nel cockpit",
["Managementsicht anonymisiert Personendaten fuer Fuehrungsberichte."] = "La vista management anonimizza i dati personali per i report direzionali.",
["Dateistatus zeigt Pfad, Zeilen, Aenderungsdatum, Alter und Frische."] = "Lo stato file mostra percorso, righe, data modifica, età e freschezza.",
["HR-Ampel fasst Fluktuation, Krankheit, GLZ, Restferien und Datenqualitaet zusammen."] = "Lo stato HR riassume turnover, malattia, saldo orario, ferie residue e qualità dati.",
["GLZ- und Restferien-Ampeln koennen gefiltert werden."] = "Gli stati di saldo orario e ferie residue possono essere filtrati.",
["Periodenvergleich zeigt die wichtigsten Vorjahreswerte, soweit Daten vorhanden sind."] = "Il confronto periodi mostra i principali valori dell'anno precedente quando disponibili.",
["Datenqualitaet markiert fehlende Dateien, alte Dateien und auffaellige Werte."] = "La qualità dati segnala file mancanti, file vecchi e valori anomali.",
["Austritte werden nach Austrittsart und Organisation gruppiert."] = "Le uscite sono raggruppate per tipo di uscita e organizzazione.",
["Absenzen werden nach Organisation ausgewertet."] = "Le assenze sono analizzate per organizzazione.",
["Top-Absenzen und kritische Detailtabellen helfen bei der operativen Pruefung."] = "Le assenze principali e le tabelle critiche supportano il controllo operativo.",
["Drucken/PDF erzeugt eine weitergebbare Ansicht aus dem Browser."] = "Stampa/PDF crea dal browser una vista condivisibile.",
["Erwartete Dateien"] = "File attesi",
["Inhalt"] = "Contenuto",
["Datei/Pfad"] = "File/percorso",
["gefunden"] = "trovato",
["fehlt"] = "manca",
["Source Viewer"] = "Visualizzatore sorgente",
["Zurueck zur Transformation"] = "Torna alle trasformazioni",
["Datei:"] = "File:",
["Klasse:"] = "Classe:",
["bei Zeile"] = "alla riga",
["Kein Dateipfad angegeben."] = "Nessun percorso file indicato.",
["Ungueltiger Dateipfad."] = "Percorso file non valido.",
["Transformer Ansicht"] = "Vista trasformazioni",
["Transformationscode"] = "Codice trasformazione",
["Keine Beschreibung."] = "Nessuna descrizione.",
["Optionales Argument."] = "Argomento opzionale."
},
["hi"] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
["Trafag Finance/Sales Management Cockpit"] = "Trafag वित्त और बिक्री प्रबंधन कॉकपिट",
["Finance Cockpit"] = "वित्त कॉकपिट",
["Finance Cockpit ist geschuetzt. Bitte separat anmelden."] = "वित्त कॉकपिट सुरक्षित है. कृपया अलग से साइन इन करें.",
["Finance-Cockpit-Zugang ist noch nicht konfiguriert. Bitte Username und PasswordHash in FinanceCockpitAccess konfigurieren."] = "वित्त कॉकपिट एक्सेस अभी कॉन्फ़िगर नहीं है. कृपया FinanceCockpitAccess में Username और PasswordHash सेट करें.",
["Export Dashboard"] = "निर्यात डैशबोर्ड",
["Management Analyse"] = "प्रबंधन विश्लेषण",
["Soll/Ist Vergleich"] = "वास्तविक/संदर्भ तुलना",
["Manuelle Importe"] = "मैनुअल आयात",
["Admin"] = "प्रशासन",
["Standorte"] = "साइटें",
["Transformationen"] = "रूपांतरण",
["Finance Regeln"] = "वित्त नियम",
["Settings"] = "सेटिंग्स",
["Logs"] = "लॉग",
["Finance sperren"] = "वित्त लॉक करें",
["HR KPI (Login)"] = "HR KPI (लॉगिन)",
["HR KPI enthaelt sensible Personaldaten. Bitte separat anmelden."] = "HR KPI में संवेदनशील कर्मचारी डेटा है. कृपया अलग से साइन इन करें.",
["HR-KPI-Zugang ist noch nicht konfiguriert. Bitte Username und PasswordHash in HrKpiAccess konfigurieren."] = "HR KPI एक्सेस अभी कॉन्फ़िगर नहीं है. कृपया HrKpiAccess में Username और PasswordHash सेट करें.",
["HR KPI entsperren"] = "HR KPI अनलॉक करें",
["HR-KPI-Anmeldung fehlgeschlagen."] = "HR KPI साइन-इन विफल.",
["Name"] = "नाम",
["Passwort"] = "पासवर्ड",
["Finance Cockpit entsperren"] = "वित्त कॉकपिट अनलॉक करें",
["Finance-Jahr"] = "वित्त वर्ष",
["Finance Summary laden"] = "वित्त सारांश लोड करें",
["Finance Summary"] = "वित्त सारांश",
["Net Sales Actual"] = "वास्तविक शुद्ध बिक्री",
["gefiltertes Endergebnis"] = "फ़िल्टर किया अंतिम परिणाम",
["Enthaltene Zeilen"] = "शामिल पंक्तियां",
["Finance Include = TRUE"] = "Finance Include = TRUE",
["Finance-Regeln"] = "वित्त नियम",
["Laender / Waehrungen"] = "देश / मुद्राएं",
["Summen wie im Excel-Blatt Finance Summary"] = "Excel शीट Finance Summary जैसे कुल",
["Keine Finance-Summary-Daten fuer diese Filter."] = "इन फ़िल्टरों के लिए कोई वित्त सारांश डेटा नहीं है.",
["Jahresvergleich mit aktuellem Filter"] = "वर्तमान फ़िल्टर के साथ वार्षिक तुलना",
["Rohdaten Diagnose"] = "कच्चे डेटा का निदान",
["Diese Analyse ist eine Plausibilitaets- und Rohdatensicht. Fuer den verbindlichen Finance-Abgleich bitte `Soll/Ist Vergleich` oder im Endexcel die `Finance | ...`-Spalten verwenden."] = "यह विश्लेषण विश्वसनीयता और कच्चे डेटा की दृश्य है. बाध्यकारी वित्त मिलान के लिए `वास्तविक/संदर्भ तुलना` या अंतिम Excel में `Finance | ...` कॉलम उपयोग करें.",
["Diese Regeln wirken nur auf die Finance-Sicht im zentralen Excel und im Abgleich. Rohdaten und Spaltenmapping bleiben unveraendert."] = "ये नियम केवल केंद्रीय Excel और मिलान की वित्त दृश्य पर लागू होते हैं. कच्चा डेटा और कॉलम मैपिंग अपरिवर्तित रहते हैं.",
["Finance-Cockpit-Anmeldung fehlgeschlagen."] = "वित्त कॉकपिट साइन-इन विफल.",
["Alle exportieren"] = "सभी निर्यात करें",
["Zentrale Datei neu erzeugen"] = "केंद्रीय फ़ाइल फिर बनाएं",
["Aktualisieren"] = "ताज़ा करें",
["Lade..."] = "लोड हो रहा है...",
["Speichern"] = "सहेजें",
["Schliessen"] = "बंद करें",
["Regel hinzufuegen"] = "नियम जोड़ें",
["Alle speichern"] = "सभी सहेजें",
["Code anzeigen"] = "कोड दिखाएं",
["Land"] = "देश",
["Laender"] = "देश",
["Aktiv"] = "सक्रिय",
["Aktionen"] = "कार्रवाई",
["Status"] = "स्थिति",
["Basis"] = "आधार",
["Quelle"] = "स्रोत",
["Schema"] = "स्कीमा",
["Server"] = "सर्वर",
["Zeilen"] = "पंक्तियां",
["Rechnungen"] = "चालान",
["Waehrung"] = "मुद्रा",
["Differenz"] = "अंतर",
["Berechnung"] = "गणना",
["Varianten"] = "वेरिएंट",
["Referenz"] = "संदर्भ",
["Ist 2025"] = "वास्तविक 2025",
["Ampel"] = "स्थिति",
["Ohne Ist"] = "खाली वास्तविक मानों के बिना",
["Varianten anzeigen"] = "वेरिएंट दिखाएं",
["Abgrenzung"] = "सीमा",
["Wert"] = "मूल्य",
["Jahr"] = "वर्ष",
["Monat"] = "महीना",
["Tag"] = "दिन",
["Hinweise"] = "नोट्स",
["Enthalten"] = "शामिल",
["Ausgeschlossen"] = "बहिष्कृत",
["Gefiltert"] = "फ़िल्टर किया गया",
["Global"] = "वैश्विक",
["Top Kunden"] = "शीर्ष ग्राहक",
["Top Produktgruppen"] = "शीर्ष उत्पाद समूह",
["Datenqualitaet"] = "डेटा गुणवत्ता",
["Management Aussagen"] = "प्रबंधन कथन",
["Nicht umgerechnet"] = "परिवर्तित नहीं",
["Jahreswerte"] = "वार्षिक मान",
["Monatswerte"] = "मासिक मान",
["Werte nach Quelle"] = "स्रोत के अनुसार मान",
["Werte nach Land"] = "देश के अनुसार मान",
["Rohdaten Diagnose"] = "कच्चे डेटा का निदान",
["Vorhandene Excel-Datei"] = "उपलब्ध Excel फ़ाइल",
["Summenfeld"] = "मान फ़ील्ड",
["Anzeige-Waehrung"] = "प्रदर्शन मुद्रा",
["Dateien laden"] = "फ़ाइलें लोड करें",
["Cockpit erzeugen"] = "कॉकपिट बनाएं",
["Zentrale Auswertung laden"] = "केंद्रीय विश्लेषण लोड करें",
["Datei / SharePoint-Ordner"] = "फ़ाइल / SharePoint फ़ोल्डर",
["Letzter Upload"] = "अंतिम अपलोड",
["Pfad pruefen"] = "पथ जांचें",
["Importdateien"] = "आयात फ़ाइलें",
["Anleitung"] = "गाइड",
["Excel bereitstellen"] = "Excel तैयार करें",
["Speichern und aktivieren"] = "सहेजें और सक्रिय करें",
["Standort exportieren"] = "साइट निर्यात करें",
["Zentrale Excel erzeugen"] = "केंद्रीय Excel बनाएं",
["Finance pruefen"] = "वित्त जांचें",
["Datei hochladen oder SharePoint-/UNC-Pfad eintragen."] = "फ़ाइल अपलोड करें या SharePoint/UNC पथ दर्ज करें.",
["Pfad pruefen, Standort aktiv setzen und speichern."] = "पथ जांचें, साइट सक्रिय करें और सहेजें.",
["Im Export Dashboard den Standort starten. Die Daten landen in CentralSalesRecords."] = "निर्यात डैशबोर्ड में साइट शुरू करें. डेटा CentralSalesRecords में लिखा जाएगा.",
["Danach `Zentrale Datei neu erzeugen` ausfuehren."] = "इसके बाद `केंद्रीय फ़ाइल फिर बनाएं` चलाएं.",
["Im Endexcel `Finance | ...` oder im Reiter `Soll/Ist Vergleich` kontrollieren."] = "अंतिम Excel में `Finance | ...` कॉलम या `वास्तविक/संदर्भ तुलना` टैब जांचें.",
["Richtige Reihenfolge"] = "सही क्रम",
["Ein Standortexport aktualisiert die Datenbasis. Die zentrale Excel muss danach neu erzeugt werden."] = "साइट निर्यात डेटा आधार अपडेट करता है. इसके बाद केंद्रीय Excel फिर बनाना होगा.",
["DE bleibt fachlich offen"] = "DE कार्यात्मक रूप से अभी खुला है",
["Alphaplan ist technisch importierbar. Kundenlaender und Filter fuer den offiziellen DE-Istwert muessen noch bestaetigt werden."] = "Alphaplan तकनीकी रूप से आयात योग्य है. आधिकारिक DE वास्तविक मान के लिए ग्राहक देश और फ़िल्टर अभी पुष्टि करने हैं.",
["Server-Hinweis"] = "सर्वर नोट",
["Der Server braucht kein Microsoft Excel. XLSX/CSV wird direkt von der Anwendung gelesen."] = "सर्वर को Microsoft Excel की आवश्यकता नहीं है. एप्लिकेशन XLSX/CSV सीधे पढ़ता है.",
["Datei hochgeladen."] = "फ़ाइल अपलोड हुई.",
["Upload fehlgeschlagen"] = "अपलोड विफल",
["Speichern fehlgeschlagen"] = "सहेजना विफल",
["Ueberblick"] = "अवलोकन",
["Fluktuation"] = "टर्नओवर",
["Austritte"] = "निकास",
["Austritte nach Austrittsart"] = "निकास प्रकार के अनुसार",
["Austritte nach Organisation"] = "संगठन के अनुसार निकास",
["Absenzen"] = "अनुपस्थिति",
["Absenzen nach Organisation"] = "संगठन के अनुसार अनुपस्थिति",
["Krankheitstage"] = "बीमारी के दिन",
["Absenzen je Mitarbeiter"] = "कर्मचारी के अनुसार अनुपस्थिति",
["Personalnr."] = "कर्मचारी संख्या",
["Organisation"] = "संगठन",
["Kurz"] = "छोटा",
["Lang"] = "लंबा",
["Gesamt"] = "कुल",
["Quote"] = "दर",
["Zeit / Ferien"] = "समय / छुट्टी",
["Kritische Restferien"] = "महत्वपूर्ण बची छुट्टी",
["Rest"] = "शेष",
["Ausstehend"] = "बकाया",
["Mitarbeitende"] = "कर्मचारी",
["Datenstatus"] = "डेटा स्थिति",
["Headcount nach Organisation"] = "संगठन के अनुसार हेडकाउंट",
["HR-Ampel"] = "HR स्थिति",
["Schwere"] = "गंभीरता",
["Bereich"] = "क्षेत्र",
["Pruefpunkt"] = "जांच बिंदु",
["Anzahl"] = "संख्या",
["Hinweis"] = "नोट",
["Gruppe"] = "समूह",
["Hoechste Absenzen"] = "सबसे अधिक अनुपस्थिति",
["Kritische GLZ-Saldi"] = "महत्वपूर्ण समय शेष",
["Saldo"] = "शेष",
["Austritt"] = "निकास",
["Austrittsart"] = "निकास प्रकार",
["Ausschlussgruende"] = "बहिष्करण कारण",
["Grund"] = "कारण",
["Kostenstelle"] = "लागत केंद्र",
["Alter"] = "आयु",
["Dienstjahre"] = "सेवा वर्ष",
["Typ"] = "प्रकार",
["Dateistatus"] = "फ़ाइल स्थिति",
["Stand"] = "संशोधित",
["Rexx exportieren"] = "Rexx से निर्यात करें",
["Die benoetigten Rexx-Abfragen manuell herunterladen. Excel/XLSX verwenden, nicht PDF."] = "ज़रूरी Rexx क्वेरी मैन्युअल रूप से डाउनलोड करें. Excel/XLSX इस्तेमाल करें, PDF नहीं.",
["Dateien ablegen"] = "फ़ाइलें रखें",
["Downloads in den Datenordner kopieren und exakt wie unten benennen."] = "डाउनलोड को डेटा फ़ोल्डर में कॉपी करें और नीचे दिए गए नामों से ही सहेजें.",
["Cockpit laden"] = "कॉकपिट लोड करें",
["Im HR-KPI-Cockpit den Datenordner kontrollieren und Laden klicken."] = "HR-KPI कॉकपिट में डेटा फ़ोल्डर जांचें और लोड पर क्लिक करें.",
["Datenstatus pruefen"] = "डेटा स्थिति जांचें",
["Im Reiter Datenstatus muessen die erwarteten Dateien gruen erscheinen."] = "डेटा स्थिति टैब में अपेक्षित फ़ाइलें हरी दिखनी चाहिए.",
["Datenordner"] = "डेटा फ़ोल्डर",
["Der Standardordner ist konfigurierbar. Fuer einen anderen Ordner oben im HR-KPI-Filter den Datenordner anpassen und neu laden."] = "डिफ़ॉल्ट फ़ोल्डर कॉन्फ़िगर किया जा सकता है. दूसरा फ़ोल्डर उपयोग करने के लिए ऊपर HR KPI फ़िल्टर में डेटा फ़ोल्डर बदलें और फिर लोड करें.",
["HR-Dateien enthalten Personendaten. Nicht per E-Mail weiterleiten und keine Kopien in ungeschuetzten Ordnern liegen lassen."] = "HR फ़ाइलों में व्यक्तिगत डेटा होता है. इन्हें ई-मेल से आगे न भेजें और असुरक्षित फ़ोल्डरों में कॉपी न छोड़ें.",
["Neue Auswertungen im Cockpit"] = "कॉकपिट में नई दृश्यावलियां",
["Managementsicht anonymisiert Personendaten fuer Fuehrungsberichte."] = "प्रबंधन दृश्य रिपोर्टों के लिए व्यक्तिगत डेटा को अनाम करता है.",
["Dateistatus zeigt Pfad, Zeilen, Aenderungsdatum, Alter und Frische."] = "फ़ाइल स्थिति पथ, पंक्तियां, संशोधन तिथि, आयु और ताजगी दिखाती है.",
["HR-Ampel fasst Fluktuation, Krankheit, GLZ, Restferien und Datenqualitaet zusammen."] = "HR स्थिति टर्नओवर, बीमारी, समय शेष, छुट्टी शेष और डेटा गुणवत्ता को सारांशित करती है.",
["GLZ- und Restferien-Ampeln koennen gefiltert werden."] = "समय शेष और छुट्टी शेष स्थितियों को फ़िल्टर किया जा सकता है.",
["Periodenvergleich zeigt die wichtigsten Vorjahreswerte, soweit Daten vorhanden sind."] = "अवधि तुलना उपलब्ध डेटा के अनुसार पिछले वर्ष के मुख्य मान दिखाती है.",
["Datenqualitaet markiert fehlende Dateien, alte Dateien und auffaellige Werte."] = "डेटा गुणवत्ता गुम फ़ाइलें, पुरानी फ़ाइलें और असामान्य मान चिह्नित करती है.",
["Austritte werden nach Austrittsart und Organisation gruppiert."] = "निकासों को निकास प्रकार और संगठन के अनुसार समूहित किया जाता है.",
["Absenzen werden nach Organisation ausgewertet."] = "अनुपस्थितियों का मूल्यांकन संगठन के अनुसार किया जाता है.",
["Top-Absenzen und kritische Detailtabellen helfen bei der operativen Pruefung."] = "शीर्ष अनुपस्थितियां और महत्वपूर्ण विवरण तालिकाएं संचालन जांच में मदद करती हैं.",
["Drucken/PDF erzeugt eine weitergebbare Ansicht aus dem Browser."] = "प्रिंट/PDF ब्राउज़र से साझा करने योग्य दृश्य बनाता है.",
["Erwartete Dateien"] = "अपेक्षित फ़ाइलें",
["Inhalt"] = "सामग्री",
["Datei/Pfad"] = "फ़ाइल/पथ",
["gefunden"] = "मिला",
["fehlt"] = "गुम",
["Source Viewer"] = "स्रोत दर्शक",
["Zurueck zur Transformation"] = "रूपांतरण पर वापस",
["Datei:"] = "फ़ाइल:",
["Klasse:"] = "क्लास:",
["bei Zeile"] = "पंक्ति पर",
["Kein Dateipfad angegeben."] = "कोई फ़ाइल पथ नहीं दिया गया.",
["Ungueltiger Dateipfad."] = "अमान्य फ़ाइल पथ.",
["Transformer Ansicht"] = "रूपांतरण दृश्य",
["Transformationscode"] = "रूपांतरण कोड",
["Keine Beschreibung."] = "कोई विवरण नहीं.",
["Optionales Argument."] = "वैकल्पिक तर्क."
}
};
public string CurrentLanguage => _currentLanguage; public string CurrentLanguage => _currentLanguage;
@@ -18,7 +634,7 @@ public sealed class UiTextService : IUiTextService
public void SetLanguage(string language) public void SetLanguage(string language)
{ {
var normalized = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase) ? "en" : "de"; var normalized = NormalizeLanguage(language);
if (string.Equals(_currentLanguage, normalized, StringComparison.OrdinalIgnoreCase)) if (string.Equals(_currentLanguage, normalized, StringComparison.OrdinalIgnoreCase))
return; return;
@@ -27,5 +643,23 @@ public sealed class UiTextService : IUiTextService
} }
public string Text(string german, string english) public string Text(string german, string english)
=> string.Equals(_currentLanguage, "en", StringComparison.OrdinalIgnoreCase) ? english : german; {
if (string.Equals(_currentLanguage, "de", StringComparison.OrdinalIgnoreCase))
return german;
if (string.Equals(_currentLanguage, "en", StringComparison.OrdinalIgnoreCase))
return english;
return Translations.TryGetValue(_currentLanguage, out var languageTranslations) &&
languageTranslations.TryGetValue(german, out var translated)
? translated
: english;
}
private static string NormalizeLanguage(string language)
{
var normalized = (language ?? string.Empty).Trim().ToLowerInvariant();
if (normalized is "in" or "ind" or "india" or "hindi")
normalized = "hi";
return SupportedLanguages.Contains(normalized) ? normalized : "de";
}
} }
@@ -44,6 +44,17 @@ public class ExcelExportServiceTests
Assert.Equal(2, includedGermanyRows.Count); Assert.Equal(2, includedGermanyRows.Count);
Assert.Equal(80m, includedGermanyRows.Sum(row => row.Cell(39).GetValue<decimal>())); Assert.Equal(80m, includedGermanyRows.Sum(row => row.Cell(39).GetValue<decimal>()));
var details = workbook.Worksheet("Finance Details");
var includedGermanyDetailRows = details.RowsUsed()
.Where(row => row.RowNumber() > 4)
.Where(row => row.Cell(1).GetValue<int>() == 2025)
.Where(row => row.Cell(2).GetString() == "DE")
.ToList();
Assert.Equal(2, includedGermanyDetailRows.Count);
Assert.Equal(80m, includedGermanyDetailRows.Sum(row => row.Cell(5).GetValue<decimal>()));
Assert.All(includedGermanyDetailRows, row => Assert.Equal("Sales Price/Value", row.Cell(6).GetString()));
} }
finally finally
{ {
@@ -129,7 +129,7 @@ public class ManualExcelDataSourceAdapterTests
public string LastResolvedTsc { get; private set; } = string.Empty; public string LastResolvedTsc { get; private set; } = string.Empty;
public Task UploadAsync(string tenantId, string clientId, string clientSecret, string siteUrl, string exportFolder, string land, string localFilePath) public Task UploadAsync(string tenantId, string clientId, string clientSecret, string siteUrl, string exportFolder, string land, string localFilePath, bool uploadTimestampedCopyIfLocked = false)
=> Task.CompletedTask; => Task.CompletedTask;
public Task<string> DownloadToTempFileAsync(string tenantId, string clientId, string clientSecret, string siteUrl, string fileReference) public Task<string> DownloadToTempFileAsync(string tenantId, string clientId, string clientSecret, string siteUrl, string fileReference)
@@ -0,0 +1,27 @@
using TrafagSalesExporter.Services;
namespace TrafagSalesExporter.Tests;
public class UiTextServiceTests
{
[Fact]
public void Text_Returns_Selected_Language_Or_English_Fallback()
{
var service = new UiTextService();
Assert.Equal("Standorte", service.Text("Standorte", "Sites"));
service.SetLanguage("en");
Assert.Equal("Sites", service.Text("Standorte", "Sites"));
service.SetLanguage("es");
Assert.Equal("Sitios", service.Text("Standorte", "Sites"));
service.SetLanguage("it");
Assert.Equal("Sedi", service.Text("Standorte", "Sites"));
service.SetLanguage("hi");
Assert.Equal("साइटें", service.Text("Standorte", "Sites"));
Assert.Equal("Untranslated English", service.Text("Nicht uebersetzt", "Untranslated English"));
}
}
+18
View File
@@ -12,3 +12,21 @@ html, body {
width: auto; width: auto;
object-fit: contain; object-fit: contain;
} }
.language-menu {
color: #fff;
}
.language-button {
min-width: 84px;
height: 34px;
border-color: rgba(255, 255, 255, 0.65) !important;
color: #fff !important;
text-transform: none;
font-weight: 700;
}
.language-button .mud-button-icon-start,
.language-button .mud-button-icon-end {
color: #fff;
}