Add new Backend structure with proper .NET 8 projects
This commit is contained in:
19
src/Backend/DiunaBI.Core/Models/DataInbox.cs
Normal file
19
src/Backend/DiunaBI.Core/Models/DataInbox.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace DiunaBI.Core.Models;
|
||||
|
||||
public class DataInbox
|
||||
{
|
||||
#region Properties
|
||||
[Key]
|
||||
public Guid Id { get; set; }
|
||||
[StringLength(50)]
|
||||
public required string Name { get; init; }
|
||||
[StringLength(50)]
|
||||
public required string Source { get; set; }
|
||||
[StringLength(int.MaxValue)]
|
||||
public required string Data { get; init; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
#endregion
|
||||
}
|
||||
46
src/Backend/DiunaBI.Core/Models/Layer.cs
Normal file
46
src/Backend/DiunaBI.Core/Models/Layer.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace DiunaBI.Core.Models;
|
||||
|
||||
public enum LayerType
|
||||
{
|
||||
Import,
|
||||
Processed,
|
||||
Administration,
|
||||
Dictionary,
|
||||
}
|
||||
public class Layer
|
||||
{
|
||||
#region Properties
|
||||
[Key]
|
||||
public Guid Id { get; init; }
|
||||
[Required]
|
||||
public int Number { get; init; }
|
||||
[Required]
|
||||
[MaxLength(50)]
|
||||
public string? Name { get; set; }
|
||||
[Required]
|
||||
public LayerType Type { get; init; }
|
||||
[Required]
|
||||
public DateTime CreatedAt { get; set; }
|
||||
[Required]
|
||||
public DateTime ModifiedAt { get; set; }
|
||||
[Required]
|
||||
public bool IsDeleted { get; init; } = false;
|
||||
[Required]
|
||||
public bool IsCancelled { get; init; } = false;
|
||||
#endregion
|
||||
#region Relations
|
||||
public ICollection<Record>? Records { get; init; }
|
||||
[Required]
|
||||
public Guid CreatedById { get; set; }
|
||||
public User? CreatedBy { get; init; }
|
||||
[Required]
|
||||
public Guid ModifiedById { get; set; }
|
||||
public User? ModifiedBy { get; init; }
|
||||
public Guid? ParentId { get; init; }
|
||||
public Layer? Parent { get; init; }
|
||||
#endregion
|
||||
}
|
||||
34
src/Backend/DiunaBI.Core/Models/LogEntry.cs
Normal file
34
src/Backend/DiunaBI.Core/Models/LogEntry.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
|
||||
namespace DiunaBI.Core.Models;
|
||||
|
||||
public enum LogEntryType
|
||||
{
|
||||
Info,
|
||||
Warning,
|
||||
Error
|
||||
}
|
||||
public enum LogType
|
||||
{
|
||||
Import,
|
||||
Backup,
|
||||
Process,
|
||||
PowerBi,
|
||||
DataInbox,
|
||||
Queue
|
||||
}
|
||||
|
||||
public enum LogInstance
|
||||
{
|
||||
Morska
|
||||
}
|
||||
public class LogEntry
|
||||
{
|
||||
public LogType LogType { get; init; }
|
||||
public LogEntryType Type { get; init; }
|
||||
public string? Message { get; init; }
|
||||
public string? Title { get; init; }
|
||||
public DateTime CreatedAt { get; init; }
|
||||
public Guid SessionId { get; set; }
|
||||
public LogInstance Instance { get; set; }
|
||||
}
|
||||
15
src/Backend/DiunaBI.Core/Models/ProcessSource.cs
Normal file
15
src/Backend/DiunaBI.Core/Models/ProcessSource.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace DiunaBI.Core.Models;
|
||||
|
||||
public class ProcessSource
|
||||
{
|
||||
#region Relations
|
||||
[Required]
|
||||
public Guid LayerId { get; init; }
|
||||
[Required]
|
||||
public Guid SourceId { get; init; }
|
||||
public Layer? Source { get; init; }
|
||||
#endregion
|
||||
}
|
||||
29
src/Backend/DiunaBI.Core/Models/QueueJob.cs
Normal file
29
src/Backend/DiunaBI.Core/Models/QueueJob.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace DiunaBI.Core.Models;
|
||||
|
||||
public enum JobStatus
|
||||
{
|
||||
New,
|
||||
Failed,
|
||||
Success
|
||||
}
|
||||
|
||||
public enum JobType
|
||||
{
|
||||
ImportWorker,
|
||||
ProcessWorker
|
||||
}
|
||||
|
||||
public class QueueJob
|
||||
{
|
||||
[Key] public Guid Id { get; set; }
|
||||
[Required] public Guid LayerId { get; set; }
|
||||
[Required] public int Attempts { get; set; }
|
||||
[Required] public JobStatus Status { get; set; } = JobStatus.New;
|
||||
[Required] public JobType Type { get; set; } = JobType.ImportWorker;
|
||||
public string Message { get; set; } = string.Empty;
|
||||
[Required] public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
[Required] public DateTime ModifiedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
62
src/Backend/DiunaBI.Core/Models/Record.cs
Normal file
62
src/Backend/DiunaBI.Core/Models/Record.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace DiunaBI.Core.Models;
|
||||
|
||||
public class Record
|
||||
{
|
||||
#region Properties
|
||||
[Key]
|
||||
public Guid Id { get; set; }
|
||||
[Required]
|
||||
[StringLength(50)]
|
||||
public string? Code { get; init; }
|
||||
public double? Value1 { get; set; }
|
||||
public double? Value2 { get; set; }
|
||||
public double? Value3 { get; set; }
|
||||
public double? Value4 { get; set; }
|
||||
public double? Value5 { get; set; }
|
||||
public double? Value6 { get; set; }
|
||||
public double? Value7 { get; set; }
|
||||
public double? Value8 { get; set; }
|
||||
public double? Value9 { get; set; }
|
||||
public double? Value10 { get; set; }
|
||||
public double? Value11 { get; set; }
|
||||
public double? Value12 { get; set; }
|
||||
public double? Value13 { get; set; }
|
||||
public double? Value14 { get; set; }
|
||||
public double? Value15 { get; set; }
|
||||
public double? Value16 { get; set; }
|
||||
public double? Value17 { get; set; }
|
||||
public double? Value18 { get; set; }
|
||||
public double? Value19 { get; set; }
|
||||
public double? Value20 { get; set; }
|
||||
public double? Value21 { get; set; }
|
||||
public double? Value22 { get; set; }
|
||||
public double? Value23 { get; set; }
|
||||
public double? Value24 { get; set; }
|
||||
public double? Value25 { get; set; }
|
||||
public double? Value26 { get; set; }
|
||||
public double? Value27 { get; set; }
|
||||
public double? Value28 { get; set; }
|
||||
public double? Value29 { get; set; }
|
||||
public double? Value30 { get; set; }
|
||||
public double? Value31 { get; set; }
|
||||
public double? Value32 { get; set; }
|
||||
//Description fields
|
||||
[StringLength(10000)]
|
||||
public string? Desc1 { get; init; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime ModifiedAt { get; set; }
|
||||
public bool IsDeleted { get; init; }
|
||||
#endregion
|
||||
#region Relations
|
||||
[Required]
|
||||
public Guid CreatedById { get; set; }
|
||||
public User? CreatedBy { get; init; }
|
||||
[Required]
|
||||
public Guid ModifiedById { get; set; }
|
||||
public User? ModifiedBy { get; init; }
|
||||
public Guid LayerId { get; set; }
|
||||
#endregion
|
||||
}
|
||||
17
src/Backend/DiunaBI.Core/Models/User.cs
Normal file
17
src/Backend/DiunaBI.Core/Models/User.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace DiunaBI.Core.Models;
|
||||
|
||||
public class User
|
||||
{
|
||||
#region Properties
|
||||
[Key]
|
||||
public Guid Id { get; init; }
|
||||
[StringLength(50)]
|
||||
public string? Email { get; init; }
|
||||
[StringLength(50)]
|
||||
public string? UserName { get; init; }
|
||||
public DateTime CreatedAt { get; init; }
|
||||
#endregion
|
||||
}
|
||||
146
src/Backend/DiunaBI.Core/Services/Calculations/BaseCalc.cs
Normal file
146
src/Backend/DiunaBI.Core/Services/Calculations/BaseCalc.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using AngouriMath;
|
||||
using DiunaBI.Core.Models;
|
||||
|
||||
namespace DiunaBI.Core.Services.Calculations;
|
||||
|
||||
public class BaseCalc
|
||||
{
|
||||
public string Expression { get; }
|
||||
private string ResultCode { get; set; }
|
||||
private string Formula { get; }
|
||||
|
||||
public BaseCalc(string expression)
|
||||
{
|
||||
Expression = expression;
|
||||
Formula = Expression.Split("=")[1];
|
||||
ResultCode = Expression.Split("=")[0];
|
||||
}
|
||||
|
||||
public bool IsFormulaCorrect()
|
||||
{
|
||||
// check left side of expression
|
||||
if (!ResultCode.StartsWith('[') || !ResultCode.EndsWith(']'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ResultCode.Substring(1, ResultCode.Length - 2).All(char.IsDigit))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ResultCode = ResultCode.Substring(1, ResultCode.Length - 2);
|
||||
|
||||
// check right side of expression
|
||||
return !string.IsNullOrEmpty(Formula) &&
|
||||
Formula.All(c => char.IsDigit(c) || c == '[' || c == ']' || c == '+' || c == '-');
|
||||
}
|
||||
|
||||
public Record CalculateT3(List<Record> records)
|
||||
{
|
||||
var resultCode = ResultCode;
|
||||
{
|
||||
var result = new Record
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Code = resultCode,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
ModifiedAt = DateTime.UtcNow
|
||||
};
|
||||
var codes = GetCodes();
|
||||
var ingredients = new List<Record>();
|
||||
foreach (var code in codes)
|
||||
{
|
||||
var ingredient = records.FirstOrDefault(r => r.Code == code);
|
||||
if (ingredient == null)
|
||||
{
|
||||
throw new Exception($"Record for code {code} not found.");
|
||||
}
|
||||
|
||||
ingredients.Add(ingredient);
|
||||
}
|
||||
|
||||
for (var i = 1; i <= 32; i++)
|
||||
{
|
||||
var formula = ingredients.Aggregate(Formula,
|
||||
(current, ingredient) => current.Replace($"[{ingredient.Code}]",
|
||||
ProcessHelper.GetValue(ingredient, i)?.ToString(CultureInfo.InvariantCulture)));
|
||||
if (formula.Contains('['))
|
||||
{
|
||||
throw new Exception($"Not all placeholders were replaced. Value{i} [{formula}]");
|
||||
}
|
||||
|
||||
Entity expr = formula;
|
||||
ProcessHelper.SetValue(result, i, (double)expr.EvalNumerical());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public Record CalculateT1(List<Record> records)
|
||||
{
|
||||
var resultCode = ResultCode;
|
||||
{
|
||||
var result = new Record
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Code = resultCode,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
ModifiedAt = DateTime.UtcNow
|
||||
};
|
||||
var codes = GetCodes();
|
||||
var ingredients = new List<Record>();
|
||||
foreach (var code in codes)
|
||||
{
|
||||
var ingredient = records.FirstOrDefault(r => r.Code == code);
|
||||
if (ingredient == null)
|
||||
{
|
||||
throw new Exception($"Record for code {code} not found.");
|
||||
}
|
||||
|
||||
ingredients.Add(ingredient);
|
||||
}
|
||||
|
||||
|
||||
var formula = ingredients.Aggregate(Formula,
|
||||
(current, ingredient) => current.Replace($"[{ingredient.Code}]",
|
||||
ProcessHelper.GetValue(ingredient, 32)?.ToString(CultureInfo.InvariantCulture)));
|
||||
if (formula.Contains('['))
|
||||
{
|
||||
throw new Exception($"Not all placeholders were replaced. Value{1} [{formula}]");
|
||||
}
|
||||
|
||||
Entity expr = formula;
|
||||
ProcessHelper.SetValue(result, 32, (double)expr.EvalNumerical());
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private List<string> GetCodes()
|
||||
{
|
||||
var codes = new List<string>();
|
||||
var endIndex = -1;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var startIndex = Formula.IndexOf("[", endIndex + 1, StringComparison.CurrentCulture);
|
||||
endIndex = Formula.IndexOf("]", startIndex + 1, StringComparison.CurrentCulture);
|
||||
|
||||
if (startIndex == -1 || endIndex == -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var valueCode = Formula.Substring(startIndex + 1, endIndex - startIndex - 1);
|
||||
codes.Add(valueCode);
|
||||
}
|
||||
|
||||
return codes;
|
||||
}
|
||||
}
|
||||
37
src/Backend/DiunaBI.Core/Services/GoogleDriveHelper.cs
Normal file
37
src/Backend/DiunaBI.Core/Services/GoogleDriveHelper.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Google.Apis.Auth.OAuth2;
|
||||
using Google.Apis.Drive.v3;
|
||||
using Google.Apis.Services;
|
||||
using System.IO;
|
||||
|
||||
namespace DiunaBI.Core.Services;
|
||||
|
||||
public class GoogleDriveHelper
|
||||
{
|
||||
public DriveService? Service { get; private set; }
|
||||
private const string ApplicationName = "Diuna";
|
||||
private static readonly string[] Scopes = [DriveService.Scope.Drive];
|
||||
public GoogleDriveHelper()
|
||||
{
|
||||
InitializeService();
|
||||
}
|
||||
private void InitializeService()
|
||||
{
|
||||
var credential = GetCredentialsFromFile();
|
||||
Service = new DriveService(new BaseClientService.Initializer
|
||||
{
|
||||
HttpClientInitializer = credential,
|
||||
ApplicationName = ApplicationName
|
||||
});
|
||||
}
|
||||
private static GoogleCredential GetCredentialsFromFile()
|
||||
{
|
||||
// ReSharper disable once RedundantAssignment
|
||||
var fileName = "client_secrets.json";
|
||||
#if DEBUG
|
||||
fileName = "client_secrets.Development.json";
|
||||
#endif
|
||||
using var stream = new FileStream(fileName, FileMode.Open, FileAccess.Read);
|
||||
var credential = GoogleCredential.FromStream(stream).CreateScoped(Scopes);
|
||||
return credential;
|
||||
}
|
||||
}
|
||||
36
src/Backend/DiunaBI.Core/Services/GoogleSheetsHelper.cs
Normal file
36
src/Backend/DiunaBI.Core/Services/GoogleSheetsHelper.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using Google.Apis.Auth.OAuth2;
|
||||
using Google.Apis.Services;
|
||||
using Google.Apis.Sheets.v4;
|
||||
using System.IO;
|
||||
|
||||
namespace DiunaBI.Core.Services;
|
||||
|
||||
public class GoogleSheetsHelper
|
||||
{
|
||||
public SheetsService? Service { get; private set; }
|
||||
private const string ApplicationName = "Diuna";
|
||||
private static readonly string[] Scopes = [SheetsService.Scope.Spreadsheets];
|
||||
public GoogleSheetsHelper()
|
||||
{
|
||||
InitializeService();
|
||||
}
|
||||
private void InitializeService()
|
||||
{
|
||||
var credential = GetCredentialsFromFile();
|
||||
Service = new SheetsService(new BaseClientService.Initializer
|
||||
{
|
||||
HttpClientInitializer = credential,
|
||||
ApplicationName = ApplicationName
|
||||
});
|
||||
}
|
||||
private static GoogleCredential GetCredentialsFromFile()
|
||||
{
|
||||
var fileName = "client_secrets.json";
|
||||
#if DEBUG
|
||||
fileName = "client_secrets.Development.json";
|
||||
#endif
|
||||
using var stream = new FileStream(fileName, FileMode.Open, FileAccess.Read);
|
||||
var credential = GoogleCredential.FromStream(stream).CreateScoped(Scopes);
|
||||
return credential;
|
||||
}
|
||||
}
|
||||
213
src/Backend/DiunaBI.Core/Services/ProcessHelper.cs
Normal file
213
src/Backend/DiunaBI.Core/Services/ProcessHelper.cs
Normal file
@@ -0,0 +1,213 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using DiunaBI.Core.Models;
|
||||
|
||||
namespace DiunaBI.Core.Services;
|
||||
public static class ProcessHelper
|
||||
{
|
||||
public static void SetValue(Record record, int number, double? value)
|
||||
{
|
||||
value = (double)Math.Round((decimal)(value ?? 0), 2);
|
||||
switch (number)
|
||||
{
|
||||
case 1:
|
||||
record.Value1 = value;
|
||||
break;
|
||||
case 2:
|
||||
record.Value2 = value;
|
||||
break;
|
||||
case 3:
|
||||
record.Value3 = value;
|
||||
break;
|
||||
case 4:
|
||||
record.Value4 = value;
|
||||
break;
|
||||
case 5:
|
||||
record.Value5 = value;
|
||||
break;
|
||||
case 6:
|
||||
record.Value6 = value;
|
||||
break;
|
||||
case 7:
|
||||
record.Value7 = value;
|
||||
break;
|
||||
case 8:
|
||||
record.Value8 = value;
|
||||
break;
|
||||
case 9:
|
||||
record.Value9 = value;
|
||||
break;
|
||||
case 10:
|
||||
record.Value10 = value;
|
||||
break;
|
||||
case 11:
|
||||
record.Value11 = value;
|
||||
break;
|
||||
case 12:
|
||||
record.Value12 = value;
|
||||
break;
|
||||
case 13:
|
||||
record.Value13 = value;
|
||||
break;
|
||||
case 14:
|
||||
record.Value14 = value;
|
||||
break;
|
||||
case 15:
|
||||
record.Value15 = value;
|
||||
break;
|
||||
case 16:
|
||||
record.Value16 = value;
|
||||
break;
|
||||
case 17:
|
||||
record.Value17 = value;
|
||||
break;
|
||||
case 18:
|
||||
record.Value18 = value;
|
||||
break;
|
||||
case 19:
|
||||
record.Value19 = value;
|
||||
break;
|
||||
case 20:
|
||||
record.Value20 = value;
|
||||
break;
|
||||
case 21:
|
||||
record.Value21 = value;
|
||||
break;
|
||||
case 22:
|
||||
record.Value22 = value;
|
||||
break;
|
||||
case 23:
|
||||
record.Value23 = value;
|
||||
break;
|
||||
case 24:
|
||||
record.Value24 = value;
|
||||
break;
|
||||
case 25:
|
||||
record.Value25 = value;
|
||||
break;
|
||||
case 26:
|
||||
record.Value26 = value;
|
||||
break;
|
||||
case 27:
|
||||
record.Value27 = value;
|
||||
break;
|
||||
case 28:
|
||||
record.Value28 = value;
|
||||
break;
|
||||
case 29:
|
||||
record.Value29 = value;
|
||||
break;
|
||||
case 30:
|
||||
record.Value30 = value;
|
||||
break;
|
||||
case 31:
|
||||
record.Value31 = value;
|
||||
break;
|
||||
case 32:
|
||||
record.Value32 = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
public static double? GetValue(Record record, int number)
|
||||
{
|
||||
return number switch
|
||||
{
|
||||
1 => record.Value1,
|
||||
2 => record.Value2,
|
||||
3 => record.Value3,
|
||||
4 => record.Value4,
|
||||
5 => record.Value5,
|
||||
6 => record.Value6,
|
||||
7 => record.Value7,
|
||||
8 => record.Value8,
|
||||
9 => record.Value9,
|
||||
10 => record.Value10,
|
||||
11 => record.Value11,
|
||||
12 => record.Value12,
|
||||
13 => record.Value13,
|
||||
14 => record.Value14,
|
||||
15 => record.Value15,
|
||||
16 => record.Value16,
|
||||
17 => record.Value17,
|
||||
18 => record.Value18,
|
||||
19 => record.Value19,
|
||||
20 => record.Value20,
|
||||
21 => record.Value21,
|
||||
22 => record.Value22,
|
||||
23 => record.Value23,
|
||||
24 => record.Value24,
|
||||
25 => record.Value25,
|
||||
26 => record.Value26,
|
||||
27 => record.Value27,
|
||||
28 => record.Value28,
|
||||
29 => record.Value29,
|
||||
30 => record.Value30,
|
||||
31 => record.Value31,
|
||||
32 => record.Value32,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
public static List<int> ParseCodes(string codes)
|
||||
{
|
||||
var codesList = new List<int>();
|
||||
foreach (var code in codes.Split(';'))
|
||||
{
|
||||
var range = code.Split('-');
|
||||
switch (range.Length)
|
||||
{
|
||||
case 1:
|
||||
codesList.Add(int.Parse(range[0]));
|
||||
break;
|
||||
case 2:
|
||||
{
|
||||
for (var i = int.Parse(range[0]); i <= int.Parse(range[1]); i++)
|
||||
{
|
||||
codesList.Add(i);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Exception($"Invalid code range: {code}");
|
||||
}
|
||||
}
|
||||
return codesList;
|
||||
}
|
||||
public static string? ExtractMonthFromLayerName(string layerName)
|
||||
{
|
||||
string pattern = @"L\d+-P-\d{4}/(\d{2})-";
|
||||
var match = Regex.Match(layerName, pattern);
|
||||
if (match.Success && match.Groups.Count > 1)
|
||||
{
|
||||
return match.Groups[1].Value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string GetSheetName(int month, int year)
|
||||
{
|
||||
if (month < 1 || month > 12)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(month), "Month must be between 1 and 12.");
|
||||
}
|
||||
|
||||
var polishMonths = new[]
|
||||
{
|
||||
"Styczen",
|
||||
"Luty",
|
||||
"Marzec",
|
||||
"Kwiecien",
|
||||
"Maj",
|
||||
"Czerwiec",
|
||||
"Lipiec",
|
||||
"Sierpien",
|
||||
"Wrzesien",
|
||||
"Pazdziernik",
|
||||
"Listopad",
|
||||
"Grudzien"
|
||||
};
|
||||
var monthName = polishMonths[month - 1];
|
||||
return $"{monthName}_{year}";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user