initial commit
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Infrastructure.Authentication.Firebase;
|
||||
using Indotalent.Infrastructure.Authentication.Identity;
|
||||
using Indotalent.Infrastructure.Authentication.Keycloak;
|
||||
using Indotalent.Infrastructure.Database;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.Text;
|
||||
|
||||
namespace Indotalent.Infrastructure.Authentication;
|
||||
|
||||
public static class DI
|
||||
{
|
||||
public static IServiceCollection AddAuthenticationService(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
//Identity
|
||||
services.AddScoped<IdentityService>();
|
||||
|
||||
services.AddHttpContextAccessor();
|
||||
services.AddCascadingAuthenticationState();
|
||||
|
||||
services.Configure<JwtSettingsModel>(configuration.GetSection("JwtSettings"));
|
||||
services.AddScoped<TokenProvider>();
|
||||
|
||||
var identitySettings = configuration.GetSection("IdentitySettings").Get<IdentitySettingsModel>() ?? new IdentitySettingsModel();
|
||||
|
||||
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
|
||||
{
|
||||
options.Password.RequireDigit = identitySettings.Password.RequireDigit;
|
||||
options.Password.RequiredLength = identitySettings.Password.RequiredLength;
|
||||
options.Password.RequireNonAlphanumeric = identitySettings.Password.RequireNonAlphanumeric;
|
||||
options.Password.RequireUppercase = identitySettings.Password.RequireUppercase;
|
||||
options.Password.RequireLowercase = identitySettings.Password.RequireLowercase;
|
||||
options.SignIn.RequireConfirmedAccount = identitySettings.SignIn.RequireConfirmedAccount;
|
||||
})
|
||||
.AddEntityFrameworkStores<AppDbContext>()
|
||||
.AddDefaultTokenProviders()
|
||||
.AddClaimsPrincipalFactory<CustomClaimsPrincipalFactory>();
|
||||
|
||||
services.AddScoped<AuthenticationStateProvider, IdentityRevalidatingAuthenticationStateProvider>();
|
||||
|
||||
services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultScheme = IdentityConstants.ApplicationScheme;
|
||||
options.DefaultSignInScheme = IdentityConstants.ApplicationScheme;
|
||||
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
|
||||
})
|
||||
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
|
||||
{
|
||||
var jwtSettings = configuration.GetSection("JwtSettings").Get<JwtSettingsModel>();
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
ValidateAudience = true,
|
||||
ValidateLifetime = true,
|
||||
ValidateIssuerSigningKey = true,
|
||||
ValidIssuer = jwtSettings?.Issuer,
|
||||
ValidAudience = jwtSettings?.Audience,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings?.Key ?? "PRAGMATIC_FIX_KEY_MIN_32_CHARS_LONG_2026"))
|
||||
};
|
||||
});
|
||||
|
||||
services.ConfigureApplicationCookie(options =>
|
||||
{
|
||||
options.LoginPath = identitySettings.Cookies.LoginPath;
|
||||
options.LogoutPath = identitySettings.Cookies.LogoutPath;
|
||||
options.AccessDeniedPath = identitySettings.Cookies.AccessDeniedPath;
|
||||
options.Cookie.Name = identitySettings.Cookies.Name;
|
||||
options.ExpireTimeSpan = TimeSpan.FromDays(identitySettings.Cookies.ExpireDays);
|
||||
options.Cookie.HttpOnly = true;
|
||||
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
|
||||
options.Cookie.SameSite = SameSiteMode.Lax;
|
||||
});
|
||||
|
||||
|
||||
//Firebase
|
||||
|
||||
services.AddScoped<FirebaseService>();
|
||||
|
||||
//Keycloak
|
||||
|
||||
services.AddScoped<KeycloakService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Indotalent.Infrastructure.Authentication.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Indotalent.Infrastructure.Authentication.Firebase;
|
||||
|
||||
public class FirebaseService(IOptions<IdentitySettingsModel> options)
|
||||
{
|
||||
private readonly IdentitySettingsModel _settings = options.Value;
|
||||
|
||||
public IdentitySettingsModel GetConfiguration() => _settings;
|
||||
|
||||
public FirebaseSettingsModel GetDefaultAdmin() => _settings.SsoFirebase;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Indotalent.Infrastructure.Authentication.Firebase;
|
||||
|
||||
public class FirebaseSettingsModel
|
||||
{
|
||||
public bool IsUsed { get; set; }
|
||||
public bool OpenForPublic { get; set; }
|
||||
public string ApiKey { get; set; } = string.Empty;
|
||||
public string AuthDomain { get; set; } = string.Empty;
|
||||
public string ProjectId { get; set; } = string.Empty;
|
||||
public string StorageBucket { get; set; } = string.Empty;
|
||||
public string MessagingSenderId { get; set; } = string.Empty;
|
||||
public string AppId { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Indotalent.Infrastructure.Authentication.Identity;
|
||||
|
||||
public class CookieSettings
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string LoginPath { get; set; } = string.Empty;
|
||||
public string LogoutPath { get; set; } = string.Empty;
|
||||
public string AccessDeniedPath { get; set; } = string.Empty;
|
||||
public int ExpireDays { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Indotalent.Infrastructure.Authentication.Identity;
|
||||
|
||||
public class CustomClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
|
||||
{
|
||||
public CustomClaimsPrincipalFactory(
|
||||
UserManager<ApplicationUser> userManager,
|
||||
RoleManager<IdentityRole> roleManager,
|
||||
IOptions<IdentityOptions> optionsAccessor)
|
||||
: base(userManager, roleManager, optionsAccessor)
|
||||
{
|
||||
}
|
||||
|
||||
protected override async Task<ClaimsIdentity> GenerateClaimsAsync(ApplicationUser user)
|
||||
{
|
||||
var identity = await base.GenerateClaimsAsync(user);
|
||||
|
||||
identity.AddClaim(new Claim(ClaimTypes.GivenName, user.FullName ?? string.Empty));
|
||||
|
||||
return identity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Indotalent.Infrastructure.Authentication.Identity;
|
||||
|
||||
public class DefaultAdminSettings
|
||||
{
|
||||
public string Email { get; set; } = string.Empty;
|
||||
public string Password { get; set; } = string.Empty;
|
||||
public string FullName { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Indotalent.Data.Entities;
|
||||
|
||||
namespace Indotalent.Infrastructure.Authentication.Identity;
|
||||
|
||||
public static class DefaultUserConfig
|
||||
{
|
||||
public static ApplicationUser GetAdminUser(DefaultAdminSettings settings) => new()
|
||||
{
|
||||
FullName = settings.FullName,
|
||||
UserName = settings.Email,
|
||||
Email = settings.Email,
|
||||
EmailConfirmed = true,
|
||||
CreatedAt = DateTime.Now,
|
||||
IsActive = true
|
||||
};
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Infrastructure.Authentication.Identity;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Microsoft.AspNetCore.Components.Server;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using System.Security.Claims;
|
||||
|
||||
internal sealed class IdentityRevalidatingAuthenticationStateProvider : RevalidatingServerAuthenticationStateProvider
|
||||
{
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly TokenProvider _tokenProvider;
|
||||
|
||||
public IdentityRevalidatingAuthenticationStateProvider(
|
||||
ILoggerFactory loggerFactory,
|
||||
IServiceScopeFactory scopeFactory,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
TokenProvider tokenProvider)
|
||||
: base(loggerFactory)
|
||||
{
|
||||
_scopeFactory = scopeFactory;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_tokenProvider = tokenProvider;
|
||||
|
||||
var context = httpContextAccessor.HttpContext;
|
||||
if (context != null)
|
||||
{
|
||||
var token = context.Request.Cookies["X-Auth-Token"];
|
||||
var refreshToken = context.Request.Cookies["X-Refresh-Token"];
|
||||
if (!string.IsNullOrEmpty(token))
|
||||
{
|
||||
_tokenProvider.Token = token;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(refreshToken))
|
||||
{
|
||||
_tokenProvider.RefreshToken = refreshToken;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override TimeSpan RevalidationInterval => TimeSpan.FromMinutes(30);
|
||||
|
||||
protected override async Task<bool> ValidateAuthenticationStateAsync(
|
||||
AuthenticationState authenticationState, CancellationToken cancellationToken)
|
||||
{
|
||||
await using var scopeAsync = _scopeFactory.CreateAsyncScope();
|
||||
var userManager = scopeAsync.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
|
||||
return await ValidateSecurityStampAsync(userManager, authenticationState.User);
|
||||
}
|
||||
|
||||
private async Task<bool> ValidateSecurityStampAsync(UserManager<ApplicationUser> userManager, ClaimsPrincipal principal)
|
||||
{
|
||||
var user = await userManager.GetUserAsync(principal);
|
||||
return user != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Indotalent.Infrastructure.Authentication.Firebase;
|
||||
using Indotalent.Infrastructure.Authentication.Keycloak;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Indotalent.Infrastructure.Authentication.Identity;
|
||||
|
||||
public class IdentityService(IOptions<IdentitySettingsModel> options)
|
||||
{
|
||||
private readonly IdentitySettingsModel _settings = options.Value;
|
||||
|
||||
public IdentitySettingsModel GetConfiguration() => _settings;
|
||||
|
||||
public DefaultAdminSettings GetDefaultAdmin() => _settings.DefaultAdmin;
|
||||
|
||||
public PasswordSettings GetPasswordPolicy() => _settings.Password;
|
||||
|
||||
public CookieSettings GetCookieSettings() => _settings.Cookies;
|
||||
|
||||
public SignInSettings GetSignInSettings() => _settings.SignIn;
|
||||
|
||||
public FirebaseSettingsModel GetFirebaseSettings() => _settings.SsoFirebase;
|
||||
|
||||
public KeycloakSettingsModel GetKeycloakSettings() => _settings.SsoKeycloak;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Indotalent.Infrastructure.Authentication.Firebase;
|
||||
using Indotalent.Infrastructure.Authentication.Keycloak;
|
||||
|
||||
namespace Indotalent.Infrastructure.Authentication.Identity;
|
||||
|
||||
public class IdentitySettingsModel
|
||||
{
|
||||
public PasswordSettings Password { get; set; } = new();
|
||||
public CookieSettings Cookies { get; set; } = new();
|
||||
public DefaultAdminSettings DefaultAdmin { get; set; } = new();
|
||||
public SignInSettings SignIn { get; set; } = new();
|
||||
public FirebaseSettingsModel SsoFirebase { get; set; } = new();
|
||||
public KeycloakSettingsModel SsoKeycloak { get; set; } = new();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Indotalent.Infrastructure.Authentication.Identity;
|
||||
|
||||
public class JwtSettingsModel
|
||||
{
|
||||
public string Key { get; set; } = string.Empty;
|
||||
public string Issuer { get; set; } = string.Empty;
|
||||
public string Audience { get; set; } = string.Empty;
|
||||
public int DurationInMinutes { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Indotalent.Infrastructure.Authentication.Identity;
|
||||
|
||||
public class PasswordSettings
|
||||
{
|
||||
public bool RequireDigit { get; set; }
|
||||
public int RequiredLength { get; set; }
|
||||
public bool RequireNonAlphanumeric { get; set; }
|
||||
public bool RequireUppercase { get; set; }
|
||||
public bool RequireLowercase { get; set; }
|
||||
public int RequiredUniqueChars { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Indotalent.Infrastructure.Authentication.Identity;
|
||||
|
||||
public class SignInSettings
|
||||
{
|
||||
public bool RequireConfirmedAccount { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Indotalent.Infrastructure.Authentication.Identity;
|
||||
|
||||
public class TokenProvider
|
||||
{
|
||||
public string? Token { get; set; }
|
||||
public string? RefreshToken { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Indotalent.Infrastructure.Authentication.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Indotalent.Infrastructure.Authentication.Keycloak;
|
||||
|
||||
public class KeycloakService(IOptions<IdentitySettingsModel> options)
|
||||
{
|
||||
private readonly IdentitySettingsModel _settings = options.Value;
|
||||
|
||||
public IdentitySettingsModel GetConfiguration() => _settings;
|
||||
|
||||
public KeycloakSettingsModel GetPasswordPolicy() => _settings.SsoKeycloak;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Indotalent.Infrastructure.Authentication.Keycloak;
|
||||
|
||||
public class KeycloakSettingsModel
|
||||
{
|
||||
public bool IsUsed { get; set; }
|
||||
public string Authority { get; set; } = string.Empty;
|
||||
public string ClientId { get; set; } = string.Empty;
|
||||
public string ClientSecret { get; set; } = string.Empty;
|
||||
public string RequireHttpsMetadata { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Indotalent.Infrastructure.Authorization.Identity;
|
||||
|
||||
public static class ApplicationRoles
|
||||
{
|
||||
public const string Admin = "Admin";
|
||||
public const string TenantAdmin = "Tenant Admin";
|
||||
public const string Member = "Member";
|
||||
|
||||
public static IReadOnlyList<string> AllRoles => new[] { Admin, Member, TenantAdmin };
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Infrastructure.Database;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Storage;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Data;
|
||||
|
||||
namespace Indotalent.Infrastructure.AutoNumberGenerator;
|
||||
|
||||
public class AutoNumberGeneratorService
|
||||
{
|
||||
private readonly AppDbContext _context;
|
||||
private static readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new();
|
||||
|
||||
public AutoNumberGeneratorService(AppDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<string> GenerateNextNumberAsync(
|
||||
string tenantId,
|
||||
string entityName,
|
||||
string prefixTemplate,
|
||||
string? suffixTemplate = null,
|
||||
int paddingLength = 4,
|
||||
bool useYear = true,
|
||||
bool useMonth = false,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
int? year = useYear ? now.Year : null;
|
||||
int? month = useMonth ? now.Month : null;
|
||||
|
||||
var lockKey = $"{tenantId}_{entityName}_{year}_{month}";
|
||||
var semaphore = _locks.GetOrAdd(lockKey, _ => new SemaphoreSlim(1, 1));
|
||||
|
||||
await semaphore.WaitAsync(cancellationToken);
|
||||
try
|
||||
{
|
||||
var connection = _context.Database.GetDbConnection();
|
||||
if (connection.State != ConnectionState.Open)
|
||||
{
|
||||
await connection.OpenAsync(cancellationToken);
|
||||
}
|
||||
|
||||
var currentEfTransaction = _context.Database.CurrentTransaction?.GetDbTransaction();
|
||||
|
||||
long currentSequence = 0;
|
||||
|
||||
var sql = @"
|
||||
MERGE INTO AutoNumberSequence AS Target
|
||||
USING (SELECT @TenantId AS TenantId, @EntityName AS EntityName, @Year AS Year, @Month AS Month) AS Source
|
||||
ON (Target.TenantId = Source.TenantId AND Target.EntityName = Source.EntityName
|
||||
AND (Target.Year = Source.Year OR (Target.Year IS NULL AND Source.Year IS NULL))
|
||||
AND (Target.Month = Source.Month OR (Target.Month IS NULL AND Source.Month IS NULL)))
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET Target.CurrentSequence = Target.CurrentSequence + 1
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT (Id, TenantId, EntityName, Year, Month, CurrentSequence, PrefixTemplate, SuffixTemplate, PaddingLength, IsDeleted)
|
||||
VALUES (@Id, Source.TenantId, Source.EntityName, Source.Year, Source.Month, 1, @PrefixTemplate, @SuffixTemplate, @PaddingLength, 0);
|
||||
|
||||
SELECT CurrentSequence FROM AutoNumberSequence
|
||||
WHERE TenantId = @TenantId AND EntityName = @EntityName
|
||||
AND (Year = @Year OR (Year IS NULL AND @Year IS NULL))
|
||||
AND (Month = @Month OR (Month IS NULL AND @Month IS NULL));";
|
||||
|
||||
using (var command = connection.CreateCommand())
|
||||
{
|
||||
command.CommandText = sql;
|
||||
command.CommandType = CommandType.Text;
|
||||
|
||||
if (currentEfTransaction != null)
|
||||
{
|
||||
command.Transaction = currentEfTransaction;
|
||||
}
|
||||
|
||||
command.Parameters.Add(new SqlParameter("@Id", Guid.NewGuid().ToString("N").Substring(0, 30)));
|
||||
command.Parameters.Add(new SqlParameter("@TenantId", tenantId));
|
||||
command.Parameters.Add(new SqlParameter("@EntityName", entityName));
|
||||
command.Parameters.Add(new SqlParameter("@Year", (object?)year ?? DBNull.Value));
|
||||
command.Parameters.Add(new SqlParameter("@Month", (object?)month ?? DBNull.Value));
|
||||
command.Parameters.Add(new SqlParameter("@PrefixTemplate", prefixTemplate));
|
||||
command.Parameters.Add(new SqlParameter("@SuffixTemplate", (object?)suffixTemplate ?? DBNull.Value));
|
||||
command.Parameters.Add(new SqlParameter("@PaddingLength", paddingLength));
|
||||
|
||||
var result = await command.ExecuteScalarAsync(cancellationToken);
|
||||
currentSequence = Convert.ToInt64(result);
|
||||
}
|
||||
|
||||
var formattedNumber = BuildFormattedNumber(
|
||||
entityName: entityName,
|
||||
prefixTemplate: prefixTemplate,
|
||||
suffixTemplate: suffixTemplate,
|
||||
paddingLength: paddingLength,
|
||||
sequence: currentSequence,
|
||||
now: now
|
||||
);
|
||||
|
||||
return formattedNumber;
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
if (semaphore.CurrentCount == 1) _locks.TryRemove(lockKey, out _);
|
||||
}
|
||||
}
|
||||
|
||||
private string BuildFormattedNumber(
|
||||
string entityName,
|
||||
string prefixTemplate,
|
||||
string? suffixTemplate,
|
||||
int paddingLength,
|
||||
long sequence,
|
||||
DateTime now)
|
||||
{
|
||||
string ProcessTemplate(string? template)
|
||||
{
|
||||
if (string.IsNullOrEmpty(template)) return "";
|
||||
return template
|
||||
.Replace("{EntityName}", entityName)
|
||||
.Replace("{Year}", now.Year.ToString())
|
||||
.Replace("{Year2}", now.ToString("yy"))
|
||||
.Replace("{Month}", now.Month.ToString("D2"));
|
||||
}
|
||||
|
||||
var prefix = ProcessTemplate(prefixTemplate);
|
||||
var suffix = ProcessTemplate(suffixTemplate);
|
||||
var paddedSequence = sequence.ToString($"D{paddingLength}");
|
||||
|
||||
return $"{prefix}{paddedSequence}{suffix}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
|
||||
namespace Indotalent.Infrastructure.AutoNumberGenerator;
|
||||
|
||||
public static class DI
|
||||
{
|
||||
public static IServiceCollection AddAutoNumberGeneratorService(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<AutoNumberGeneratorService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Indotalent.Infrastructure.AutoNumberGenerator;
|
||||
|
||||
public interface IAutoNumberGenerator
|
||||
{
|
||||
string? EntityName { get; set; }
|
||||
int? Year { get; set; }
|
||||
int? Month { get; set; }
|
||||
long? CurrentSequence { get; set; }
|
||||
string? PrefixTemplate { get; set; }
|
||||
string? SuffixTemplate { get; set; }
|
||||
int? PaddingLength { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Indotalent.Infrastructure.BackgroundJob;
|
||||
|
||||
public class BackgroundJobService(IOptions<BackgroundJobSettingsModel> options)
|
||||
{
|
||||
private readonly BackgroundJobSettingsModel _settings = options.Value;
|
||||
|
||||
public BackgroundJobSettingsModel GetConfiguration() => _settings;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Indotalent.Infrastructure.BackgroundJob.Hangfire;
|
||||
using Indotalent.Infrastructure.BackgroundJob.Quartz;
|
||||
|
||||
namespace Indotalent.Infrastructure.BackgroundJob;
|
||||
|
||||
public class BackgroundJobSettingsModel
|
||||
{
|
||||
public HangfireSettingsModel Hangfire { get; set; } = new();
|
||||
public QuartzSettingsModel Quartz { get; set; } = new();
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Indotalent.Infrastructure.BackgroundJob;
|
||||
|
||||
public static class DI
|
||||
{
|
||||
public static IServiceCollection AddBackgroundJobService(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<BackgroundJobService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Indotalent.Infrastructure.BackgroundJob.Hangfire;
|
||||
|
||||
public class HangfireSettingsModel
|
||||
{
|
||||
public bool IsUsed { get; set; }
|
||||
public string ConnectionString { get; set; } = string.Empty;
|
||||
public string DashboardPath { get; set; } = "/hangfire";
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Indotalent.Infrastructure.BackgroundJob.Quartz;
|
||||
|
||||
public class QuartzSettingsModel
|
||||
{
|
||||
public bool IsUsed { get; set; }
|
||||
public string TablePrefix { get; set; } = "QRTZ_";
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using Indotalent.Infrastructure.Authentication;
|
||||
using Indotalent.Infrastructure.Authentication.Identity;
|
||||
using Indotalent.Infrastructure.AutoNumberGenerator;
|
||||
using Indotalent.Infrastructure.BackgroundJob;
|
||||
using Indotalent.Infrastructure.Database;
|
||||
using Indotalent.Infrastructure.Email;
|
||||
using Indotalent.Infrastructure.File;
|
||||
using Indotalent.Infrastructure.Logging;
|
||||
using Indotalent.Infrastructure.OData;
|
||||
|
||||
namespace Indotalent.Infrastructure;
|
||||
|
||||
public static class DI
|
||||
{
|
||||
public static IServiceCollection AddInfrastructureDI(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.Configure<DatabaseSettingsModel>(configuration.GetSection("DatabaseSettings"));
|
||||
services.Configure<BackgroundJobSettingsModel>(configuration.GetSection("BackgroundJobSettings"));
|
||||
services.Configure<EmailSettingsModel>(configuration.GetSection("EmailSettings"));
|
||||
services.Configure<FileStorageSettingsModel>(configuration.GetSection("FileStorageSettings"));
|
||||
services.Configure<LoggerSettingsModel>(configuration.GetSection("LoggerSettings"));
|
||||
services.Configure<IdentitySettingsModel>(configuration.GetSection("IdentitySettings"));
|
||||
|
||||
services.AddLoggingService();
|
||||
|
||||
services.AddDatabaseService(configuration);
|
||||
|
||||
services.AddBackgroundJobService();
|
||||
|
||||
services.AddEmailService(configuration);
|
||||
|
||||
services.AddFileService();
|
||||
|
||||
services.AddAuthenticationService(configuration);
|
||||
|
||||
services.AddAutoNumberGeneratorService();
|
||||
|
||||
services.AddODataService();
|
||||
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
using Indotalent.ConfigBackEnd.Extensions;
|
||||
using Indotalent.ConfigBackEnd.Interfaces;
|
||||
using Indotalent.Data.Abstracts;
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Data.Interfaces;
|
||||
using Indotalent.Infrastructure.AutoNumberGenerator;
|
||||
using Indotalent.Shared.Consts;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Indotalent.Infrastructure.Database;
|
||||
|
||||
public class AppDbContext : IdentityDbContext<ApplicationUser>
|
||||
{
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly ICurrentUserService _currentUserService;
|
||||
|
||||
public AppDbContext(
|
||||
DbContextOptions<AppDbContext> options,
|
||||
IServiceScopeFactory scopeFactory,
|
||||
ICurrentUserService currentUserService) : base(options)
|
||||
{
|
||||
_scopeFactory = scopeFactory;
|
||||
_currentUserService = currentUserService;
|
||||
}
|
||||
|
||||
public string CurrentTenantId => _currentUserService?.TenantId ?? "NO_TENANT_SELECTED";
|
||||
|
||||
public DbSet<AutoNumberSequence> AutoNumberSequence { get; set; } = default!;
|
||||
public DbSet<Currency> Currency { get; set; } = default!;
|
||||
public DbSet<Company> Company { get; set; } = default!;
|
||||
public DbSet<Tax> Tax { get; set; } = default!;
|
||||
public DbSet<Branch> Branch { get; set; } = default!;
|
||||
public DbSet<Department> Department { get; set; } = default!;
|
||||
public DbSet<Designation> Designation { get; set; } = default!;
|
||||
public DbSet<Employee> Employee { get; set; } = default!;
|
||||
public DbSet<LeaveCategory> LeaveCategory { get; set; } = default!;
|
||||
public DbSet<LeaveRequest> LeaveRequest { get; set; } = default!;
|
||||
public DbSet<LeaveBalance> LeaveBalance { get; set; } = default!;
|
||||
public DbSet<Evaluation> Evaluation { get; set; } = default!;
|
||||
public DbSet<Appraisal> Appraisal { get; set; } = default!;
|
||||
public DbSet<Promotion> Promotion { get; set; } = default!;
|
||||
public DbSet<Transfer> Transfer { get; set; } = default!;
|
||||
public DbSet<Income> Income { get; set; } = default!;
|
||||
public DbSet<Deduction> Deduction { get; set; } = default!;
|
||||
public DbSet<Grade> Grade { get; set; } = default!;
|
||||
public DbSet<EmployeeIncome> EmployeeIncome { get; set; } = default!;
|
||||
public DbSet<EmployeeDeduction> EmployeeDeduction { get; set; } = default!;
|
||||
public DbSet<PayrollProcess> PayrollProcess { get; set; } = default!;
|
||||
public DbSet<PayrollDetail> PayrollDetail { get; set; } = default!;
|
||||
public DbSet<PayrollComponent> PayrollComponent { get; set; } = default!;
|
||||
public DbSet<Tenant> Tenant { get; set; } = default!;
|
||||
public DbSet<TenantUser> TenantUser { get; set; } = default!;
|
||||
|
||||
public override int SaveChanges()
|
||||
{
|
||||
ApplySoftDelete();
|
||||
ApplyAudit();
|
||||
ApplyMultiTenant();
|
||||
return base.SaveChanges();
|
||||
}
|
||||
|
||||
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
ChangeTracker.DetectChanges();
|
||||
ApplySoftDelete();
|
||||
ApplyAudit();
|
||||
ApplyMultiTenant();
|
||||
await ApplyAutoNumberAsync(cancellationToken);
|
||||
return await base.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
private void ApplySoftDelete()
|
||||
{
|
||||
var entries = ChangeTracker.Entries<IHasIsDeleted>()
|
||||
.Where(e => e.State == EntityState.Deleted)
|
||||
.ToList();
|
||||
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
entry.State = EntityState.Modified;
|
||||
entry.Entity.IsDeleted = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyAudit()
|
||||
{
|
||||
var entries = ChangeTracker.Entries<IHasAudit>()
|
||||
.Where(e => e.State == EntityState.Added || e.State == EntityState.Modified)
|
||||
.ToList();
|
||||
|
||||
if (!entries.Any()) return;
|
||||
|
||||
var userId = _currentUserService.UserId ?? "SYSTEM";
|
||||
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
{
|
||||
throw new InvalidOperationException("UserId not found in the current user session context.");
|
||||
}
|
||||
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var auditEntity = entry.Entity;
|
||||
|
||||
if (entry.State == EntityState.Added)
|
||||
{
|
||||
auditEntity.CreatedAt = now;
|
||||
auditEntity.CreatedBy = userId;
|
||||
}
|
||||
else
|
||||
{
|
||||
entry.Property(nameof(IHasAudit.CreatedAt)).IsModified = false;
|
||||
entry.Property(nameof(IHasAudit.CreatedBy)).IsModified = false;
|
||||
}
|
||||
|
||||
auditEntity.UpdatedAt = now;
|
||||
auditEntity.UpdatedBy = userId;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ApplyAutoNumberAsync(CancellationToken ct)
|
||||
{
|
||||
var entries = ChangeTracker
|
||||
.Entries<IHasAutoNumber>()
|
||||
.Where(e => e.State == EntityState.Added && string.IsNullOrEmpty(e.Entity.AutoNumber))
|
||||
.ToList();
|
||||
|
||||
if (!entries.Any()) return;
|
||||
|
||||
var currentTenantId = _currentUserService.TenantId ?? "SYSTEM_GLOBAL_TENANT";
|
||||
|
||||
if (string.IsNullOrWhiteSpace(currentTenantId))
|
||||
{
|
||||
throw new InvalidOperationException("TenantId cannot be null or empty for multi-tenant auto-number sequence generation. Ensure the user session context is valid.");
|
||||
}
|
||||
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var generator = scope.ServiceProvider.GetRequiredService<AutoNumberGeneratorService>();
|
||||
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
var entityName = entry.Entity.GetType().Name;
|
||||
var shortName = entityName.ToShortNameConsonant(3);
|
||||
|
||||
entry.Entity.AutoNumber = await generator.GenerateNextNumberAsync(
|
||||
tenantId: currentTenantId ?? string.Empty,
|
||||
entityName: entityName,
|
||||
prefixTemplate: $"{shortName}/{{Year}}/",
|
||||
paddingLength: 4,
|
||||
cancellationToken: ct
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyMultiTenant()
|
||||
{
|
||||
var entries = ChangeTracker.Entries<IMultiTenant>()
|
||||
.Where(e => e.State == EntityState.Added || e.State == EntityState.Modified)
|
||||
.ToList();
|
||||
|
||||
if (!entries.Any()) return;
|
||||
|
||||
var currentTenantId = _currentUserService.TenantId;
|
||||
|
||||
if (string.IsNullOrEmpty(currentTenantId))
|
||||
{
|
||||
throw new InvalidOperationException("TenantId not found in the current user session context.");
|
||||
}
|
||||
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
if (entry.State == EntityState.Added)
|
||||
{
|
||||
entry.Entity.TenantId = currentTenantId;
|
||||
}
|
||||
else if (entry.State == EntityState.Modified)
|
||||
{
|
||||
var dbTenantId = entry.Property(nameof(IMultiTenant.TenantId)).OriginalValue;
|
||||
if (dbTenantId != null)
|
||||
{
|
||||
if (entry.Entity.TenantId != dbTenantId.ToString() || entry.Entity.TenantId != currentTenantId)
|
||||
{
|
||||
throw new InvalidOperationException("Cross-tenant operation detected or TenantId data corruption attempted.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
if (Database.ProviderName?.Contains("MySql") == true)
|
||||
{
|
||||
foreach (var entity in modelBuilder.Model.GetEntityTypes())
|
||||
{
|
||||
if (entity.GetTableName()!.StartsWith("AspNet"))
|
||||
{
|
||||
foreach (var property in entity.GetProperties().Where(p => p.ClrType == typeof(string)))
|
||||
{
|
||||
property.SetMaxLength(191);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
|
||||
{
|
||||
var type = entityType.ClrType;
|
||||
|
||||
if (typeof(BaseEntity).IsAssignableFrom(type))
|
||||
{
|
||||
modelBuilder.Entity(type)
|
||||
.Property("Id")
|
||||
.HasMaxLength(GlobalConsts.StringLengthId)
|
||||
.IsFixedLength();
|
||||
}
|
||||
|
||||
if (typeof(IHasAudit).IsAssignableFrom(type))
|
||||
{
|
||||
modelBuilder.Entity(type)
|
||||
.Property(nameof(IHasAudit.CreatedBy))
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
modelBuilder.Entity(type)
|
||||
.Property(nameof(IHasAudit.UpdatedBy))
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
}
|
||||
|
||||
if (typeof(IMultiTenant).IsAssignableFrom(type))
|
||||
{
|
||||
modelBuilder.Entity(type)
|
||||
.Property(nameof(IMultiTenant.TenantId))
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
}
|
||||
|
||||
var hasDeleted = typeof(IHasIsDeleted).IsAssignableFrom(type);
|
||||
var isMultiTenant = typeof(IMultiTenant).IsAssignableFrom(type);
|
||||
|
||||
if (hasDeleted || isMultiTenant)
|
||||
{
|
||||
modelBuilder.Entity(type).HasQueryFilter(GetGlobalFilters(type, hasDeleted, isMultiTenant));
|
||||
}
|
||||
}
|
||||
|
||||
modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);
|
||||
}
|
||||
|
||||
|
||||
private System.Linq.Expressions.LambdaExpression GetGlobalFilters(Type type, bool hasDeleted, bool isMultiTenant)
|
||||
{
|
||||
var parameter = System.Linq.Expressions.Expression.Parameter(type, "e");
|
||||
System.Linq.Expressions.Expression? combinedBody = null;
|
||||
|
||||
if (hasDeleted)
|
||||
{
|
||||
var property = System.Linq.Expressions.Expression.Property(parameter, nameof(IHasIsDeleted.IsDeleted));
|
||||
var falseConstant = System.Linq.Expressions.Expression.Constant(false);
|
||||
combinedBody = System.Linq.Expressions.Expression.Equal(property, falseConstant);
|
||||
}
|
||||
|
||||
if (isMultiTenant)
|
||||
{
|
||||
var tenantProperty = System.Linq.Expressions.Expression.Property(parameter, nameof(IMultiTenant.TenantId));
|
||||
|
||||
var dbContextInstance = System.Linq.Expressions.Expression.Constant(this);
|
||||
|
||||
var currentTenantIdProperty = System.Linq.Expressions.Expression.Property(dbContextInstance, nameof(CurrentTenantId));
|
||||
|
||||
var tenantComparison = System.Linq.Expressions.Expression.Equal(tenantProperty, currentTenantIdProperty);
|
||||
|
||||
combinedBody = combinedBody == null
|
||||
? tenantComparison
|
||||
: System.Linq.Expressions.Expression.AndAlso(combinedBody, tenantComparison);
|
||||
}
|
||||
|
||||
return System.Linq.Expressions.Expression.Lambda(combinedBody!, parameter);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Indotalent.Infrastructure.Database;
|
||||
|
||||
public static class DI
|
||||
{
|
||||
public static IServiceCollection AddDatabaseService(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
var dbSettings = configuration.GetSection("DatabaseSettings").Get<DatabaseSettingsModel>();
|
||||
|
||||
if (dbSettings?.MsSQL?.IsUsed == true)
|
||||
{
|
||||
services.AddDbContext<AppDbContext>(options =>
|
||||
options.UseSqlServer(
|
||||
dbSettings.MsSQL.ConnectionString,
|
||||
sqlOptions => {
|
||||
sqlOptions.CommandTimeout(dbSettings.MsSQL.TimeoutInSeconds);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
else if (dbSettings?.PostgreSQL?.IsUsed == true)
|
||||
{
|
||||
services.AddDbContext<AppDbContext>(options =>
|
||||
options.UseNpgsql(
|
||||
dbSettings.PostgreSQL.ConnectionString,
|
||||
npgsqlOptions => {
|
||||
npgsqlOptions.CommandTimeout(dbSettings.PostgreSQL.TimeoutInSeconds);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
else if (dbSettings?.MySQL?.IsUsed == true)
|
||||
{
|
||||
services.AddDbContext<AppDbContext>(options =>
|
||||
options.UseMySQL(
|
||||
dbSettings.MySQL.ConnectionString,
|
||||
mySqlOptions => {
|
||||
mySqlOptions.CommandTimeout(dbSettings.MySQL.TimeoutInSeconds);
|
||||
mySqlOptions.MigrationsAssembly(typeof(AppDbContext).Assembly.FullName);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
services.AddScoped<DatabaseService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Indotalent.Infrastructure.Database;
|
||||
|
||||
public class DatabaseProviderModel
|
||||
{
|
||||
public bool IsUsed { get; set; }
|
||||
public string ConnectionString { get; set; } = string.Empty;
|
||||
public int TimeoutInSeconds { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,713 @@
|
||||
using Indotalent.ConfigBackEnd.Interfaces;
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Infrastructure.Authentication.Identity;
|
||||
using Indotalent.Infrastructure.Authorization.Identity;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Indotalent.Infrastructure.Database;
|
||||
|
||||
public static class DatabaseSeeder
|
||||
{
|
||||
private static string? adminUserId;
|
||||
private static string? tenantId;
|
||||
|
||||
public static async Task SeedAsync(IServiceProvider serviceProvider)
|
||||
{
|
||||
var roleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
|
||||
var userManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
|
||||
var configuration = serviceProvider.GetRequiredService<IConfiguration>();
|
||||
var context = serviceProvider.GetRequiredService<AppDbContext>();
|
||||
|
||||
var currentUserService = serviceProvider.GetRequiredService<ICurrentUserService>();
|
||||
|
||||
await SeedIdentityAsync(roleManager, userManager, configuration);
|
||||
currentUserService.UserId = adminUserId;
|
||||
|
||||
await SeedDemoTenantAsync(context);
|
||||
currentUserService.TenantId = tenantId;
|
||||
|
||||
await SeedCurrenciesAsync(context);
|
||||
await SeedCompanyAsync(context);
|
||||
await SeedTaxAsync(context);
|
||||
await SeedBranchAsync(context);
|
||||
await SeedDepartmentAsync(context);
|
||||
await SeedDesignationAsync(context);
|
||||
await SeedGradeAsync(context);
|
||||
await SeedIncomeAsync(context);
|
||||
await SeedDeductionAsync(context);
|
||||
await SeedEmployeeAsync(context);
|
||||
await SeedLeaveCategoryAsync(context);
|
||||
await SeedLeaveBalanceAsync(context);
|
||||
await SeedLeaveRequestAsync(context);
|
||||
await SeedEvaluationAsync(context);
|
||||
await SeedAppraisalAsync(context);
|
||||
await SeedPromotionAsync(context);
|
||||
await SeedTransferAsync(context);
|
||||
await SeedPayrollAsync(context);
|
||||
}
|
||||
private static async Task SeedDemoTenantAsync(AppDbContext context)
|
||||
{
|
||||
var existingTenant = await context.Set<Tenant>().FirstOrDefaultAsync(t => t.Name == "Demo Tenant");
|
||||
|
||||
if (existingTenant == null)
|
||||
{
|
||||
var newTenant = new Tenant
|
||||
{
|
||||
Name = "Demo Tenant",
|
||||
Description = "Initial system tenant for demonstration and seed data.",
|
||||
Street = "Sudirman Street No. 123",
|
||||
City = "Bandung",
|
||||
State = "West Java",
|
||||
ZipCode = "40111",
|
||||
Country = "Indonesia",
|
||||
PhoneNumber = "+62221234567",
|
||||
EmailAddress = "hi@indotalent.com",
|
||||
Website = "https://indotalent.com",
|
||||
IsActive = true
|
||||
};
|
||||
|
||||
await context.Set<Tenant>().AddAsync(newTenant);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
tenantId = newTenant.Id;
|
||||
}
|
||||
else
|
||||
{
|
||||
tenantId = existingTenant.Id;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(adminUserId) && !string.IsNullOrEmpty(tenantId))
|
||||
{
|
||||
var isMapped = await context.Set<TenantUser>()
|
||||
.AnyAsync(tu => tu.TenantId == tenantId && tu.UserId == adminUserId);
|
||||
|
||||
if (!isMapped)
|
||||
{
|
||||
var tenantUser = new TenantUser
|
||||
{
|
||||
TenantId = tenantId,
|
||||
UserId = adminUserId,
|
||||
Summary = "Administrator default member mapping",
|
||||
IsActive = true
|
||||
};
|
||||
|
||||
await context.Set<TenantUser>().AddAsync(tenantUser);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task SeedIdentityAsync(RoleManager<IdentityRole> roleManager, UserManager<ApplicationUser> userManager, IConfiguration configuration)
|
||||
{
|
||||
var adminSettings = configuration.GetSection("IdentitySettings:DefaultAdmin").Get<DefaultAdminSettings>();
|
||||
if (adminSettings == null) return;
|
||||
|
||||
foreach (var roleName in ApplicationRoles.AllRoles)
|
||||
{
|
||||
if (!await roleManager.RoleExistsAsync(roleName))
|
||||
{
|
||||
await roleManager.CreateAsync(new IdentityRole(roleName));
|
||||
}
|
||||
}
|
||||
|
||||
var existingUser = await userManager.FindByEmailAsync(adminSettings.Email);
|
||||
if (existingUser == null)
|
||||
{
|
||||
var defaultAdmin = DefaultUserConfig.GetAdminUser(adminSettings);
|
||||
var createAdmin = await userManager.CreateAsync(defaultAdmin, adminSettings.Password);
|
||||
|
||||
if (createAdmin.Succeeded)
|
||||
{
|
||||
await userManager.AddToRolesAsync(defaultAdmin, ApplicationRoles.AllRoles);
|
||||
adminUserId = defaultAdmin.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task SeedCurrenciesAsync(AppDbContext context)
|
||||
{
|
||||
if (await context.Set<Currency>().AnyAsync()) return;
|
||||
|
||||
var currencies = new List<Currency>
|
||||
{
|
||||
new() { Name = "US Dollar", Code = "USD", Symbol = "$", Description = "United States official currency", CountryOwner = "United States" },
|
||||
new() { Name = "Euro", Code = "EUR", Symbol = "€", Description = "Eurozone member states currency", CountryOwner = "European Union" },
|
||||
new() { Name = "British Pound", Code = "GBP", Symbol = "£", Description = "Official currency of the UK", CountryOwner = "United Kingdom" },
|
||||
new() { Name = "Indonesian Rupiah", Code = "IDR", Symbol = "Rp", Description = "Official currency of Indonesia", CountryOwner = "Indonesia" }
|
||||
};
|
||||
|
||||
await context.Set<Currency>().AddRangeAsync(currencies);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private static async Task SeedCompanyAsync(AppDbContext context)
|
||||
{
|
||||
if (await context.Set<Company>().AnyAsync()) return;
|
||||
|
||||
var usdCurrency = await context.Set<Currency>().FirstOrDefaultAsync(x => x.Code == "USD");
|
||||
if (usdCurrency == null) return;
|
||||
|
||||
var companies = new List<Company>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Name = "Acme Global Solutions Inc.",
|
||||
Description = "A leading provider of enterprise-grade cloud software solutions.",
|
||||
IsDefault = true,
|
||||
CurrencyId = usdCurrency.Id,
|
||||
TaxIdentification = "EIN 12-3456789",
|
||||
BusinessLicense = "LC-987654321",
|
||||
StreetAddress = "One World Trade Center, Suite 85",
|
||||
City = "New York",
|
||||
StateProvince = "NY",
|
||||
ZipCode = "10007",
|
||||
Phone = "+1 212 555 0198",
|
||||
Email = "hr@acmeglobal.com",
|
||||
SocialMediaLinkedIn = "linkedin.com/company/acme-global",
|
||||
CompanyLogo = "/images/logos/acme-logo.png",
|
||||
OtherInformation1 = "Corporate Headquarters",
|
||||
OtherInformation2 = "Technology & SaaS",
|
||||
OtherInformation3 = "Fortune 500 Candidate"
|
||||
}
|
||||
};
|
||||
|
||||
await context.Set<Company>().AddRangeAsync(companies);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private static async Task SeedTaxAsync(AppDbContext context)
|
||||
{
|
||||
if (await context.Set<Tax>().AnyAsync()) return;
|
||||
|
||||
var taxes = new List<Tax>
|
||||
{
|
||||
new() { Code = "FED-INC", Name = "Federal Income Tax", Description = "US Federal Progressive Income Tax", PercentageValue = 22, Category = "Income Tax" },
|
||||
new() { Code = "SS-TAX", Name = "Social Security", Description = "Social Security (OASDI)", PercentageValue = 6.2m, Category = "Statutory" },
|
||||
new() { Code = "MED-TAX", Name = "Medicare", Description = "Medicare Hospital Insurance", PercentageValue = 1.45m, Category = "Statutory" },
|
||||
new() { Code = "NY-STATE", Name = "NY State Tax", Description = "New York State Income Tax", PercentageValue = 5.8m, Category = "State Tax" },
|
||||
new() { Code = "NY-CITY", Name = "NYC Local Tax", Description = "New York City Local Tax", PercentageValue = 3.5m, Category = "Local Tax" }
|
||||
};
|
||||
|
||||
await context.Set<Tax>().AddRangeAsync(taxes);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private static async Task SeedBranchAsync(AppDbContext context)
|
||||
{
|
||||
if (await context.Set<Branch>().AnyAsync()) return;
|
||||
|
||||
var branches = new List<Branch>
|
||||
{
|
||||
new() { Name = "New York HQ", City = "New York", StreetAddress = "One World Trade Center", Phone = "+1 212 555 0101", Code = "US-NYC" },
|
||||
new() { Name = "San Francisco Tech Center", City = "San Francisco", StreetAddress = "Market Street, Financial District", Phone = "+1 415 555 0202", Code = "US-SFO" },
|
||||
new() { Name = "Chicago Operations", City = "Chicago", StreetAddress = "Willis Tower, 233 S Wacker Dr", Phone = "+1 312 555 0303", Code = "US-CHI" },
|
||||
new() { Name = "Austin Innovation Hub", City = "Austin", StreetAddress = "Congress Avenue, Downtown", Phone = "+1 512 555 0404", Code = "US-AUS" },
|
||||
new() { Name = "Seattle Cloud Office", City = "Seattle", StreetAddress = "Westlake Ave, South Lake Union", Phone = "+1 206 555 0505", Code = "US-SEA" }
|
||||
};
|
||||
|
||||
await context.Set<Branch>().AddRangeAsync(branches);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private static async Task SeedDepartmentAsync(AppDbContext context)
|
||||
{
|
||||
if (await context.Set<Department>().AnyAsync()) return;
|
||||
|
||||
var departments = new List<Department>
|
||||
{
|
||||
new() { CostCenter = "US-IT", Name = "Engineering & IT", HeadOfDeptartment = "Michael Scott", Description = "Software engineering and cloud infrastructure." },
|
||||
new() { CostCenter = "US-HR", Name = "People & Culture", HeadOfDeptartment = "Pam Beesly", Description = "Talent acquisition and employee experience." },
|
||||
new() { CostCenter = "US-FIN", Name = "Finance", HeadOfDeptartment = "Angela Martin", Description = "Global financial planning and analysis." },
|
||||
new() { CostCenter = "US-MKT", Name = "Marketing", HeadOfDeptartment = "Ryan Howard", Description = "Digital marketing and brand growth." },
|
||||
new() { CostCenter = "US-SAL", Name = "Global Sales", HeadOfDeptartment = "Jim Halpert", Description = "Enterprise sales and business development." }
|
||||
};
|
||||
|
||||
await context.Set<Department>().AddRangeAsync(departments);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private static async Task SeedDesignationAsync(AppDbContext context)
|
||||
{
|
||||
if (await context.Set<Designation>().AnyAsync()) return;
|
||||
|
||||
var designations = new List<Designation>
|
||||
{
|
||||
new() { Code = "C-LEVEL", Name = "Chief Executive Officer", Level = "Executive", Description = "Global strategy Lead." },
|
||||
new() { Code = "DIR-01", Name = "Director of Engineering", Level = "Director", Description = "Engineering organization Lead." },
|
||||
new() { Code = "MGR-01", Name = "Engineering Manager", Level = "Management", Description = "Team and project management." },
|
||||
new() { Code = "ENG-01", Name = "Principal Software Engineer", Level = "Professional", Description = "Systems architecture." },
|
||||
new() { Code = "ENG-02", Name = "Senior Software Engineer", Level = "Professional", Description = "Full-stack development." },
|
||||
new() { Code = "ENG-03", Name = "Cloud Architect", Level = "Professional", Description = "Azure/AWS Infrastructure." }
|
||||
};
|
||||
|
||||
await context.Set<Designation>().AddRangeAsync(designations);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private static async Task SeedIncomeAsync(AppDbContext context)
|
||||
{
|
||||
if (await context.Set<Income>().AnyAsync()) return;
|
||||
|
||||
var incomes = new List<Income>
|
||||
{
|
||||
new() { Code = "BASE", Name = "Base Salary", Type = "Fixed", IsTaxable = true, CalculationBase = "Contracted salary", Status = "Active", Description = "Monthly base compensation" },
|
||||
new() { Code = "BONUS", Name = "Performance Bonus", Type = "Variable", IsTaxable = true, CalculationBase = "Quarterly KPI Result", Status = "Active", Description = "KPI based performance incentive" },
|
||||
// Di US umum ada Stipend atau Reimbursement
|
||||
new() { Code = "WFH", Name = "WFH Stipend", Type = "Fixed", IsTaxable = false, CalculationBase = "Remote work support", Status = "Active", Description = "Internet and utilities allowance" },
|
||||
new() { Code = "CAR", Name = "Car Allowance", Type = "Fixed", IsTaxable = true, CalculationBase = "Executive car benefit", Status = "Active", Description = "Executive transportation support" }
|
||||
};
|
||||
|
||||
await context.Set<Income>().AddRangeAsync(incomes);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private static async Task SeedDeductionAsync(AppDbContext context)
|
||||
{
|
||||
if (await context.Set<Deduction>().AnyAsync()) return;
|
||||
|
||||
var deductions = new List<Deduction>
|
||||
{
|
||||
new() { Code = "FIT", Name = "Federal Income Tax", Category = "Statutory", PreTax = false, CalculationMethod = "IRS Tax Tables", Status = "Active", Description = "Mandatory Federal Tax" },
|
||||
new() { Code = "401K", Name = "401(k) Contribution", Category = "Retirement", PreTax = true, CalculationMethod = "% of Gross Salary", Status = "Active", Description = "Voluntary retirement savings" },
|
||||
new() { Code = "HLTH", Name = "Health Insurance Premium", Category = "Insurance", PreTax = true, CalculationMethod = "Flat employee portion", Status = "Active", Description = "Medical/Dental/Vision premium" },
|
||||
new() { Code = "LIFE", Name = "Group Life Insurance", Category = "Insurance", PreTax = false, CalculationMethod = "Fixed premium", Status = "Active", Description = "Optional life insurance coverage" }
|
||||
};
|
||||
|
||||
await context.Set<Deduction>().AddRangeAsync(deductions);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private static async Task SeedGradeAsync(AppDbContext context)
|
||||
{
|
||||
if (await context.Set<Grade>().AnyAsync()) return;
|
||||
|
||||
var grades = new List<Grade>
|
||||
{
|
||||
new() { Code = "L-01", Name = "Executive Level", Description = "C-Suite and VPs", SalaryFrom = 180000, SalaryTo = 350000, IsOverTimeEligible = false, Status = "Active" },
|
||||
new() { Code = "L-02", Name = "Director Level", Description = "Department Directors", SalaryFrom = 140000, SalaryTo = 200000, IsOverTimeEligible = false, Status = "Active" },
|
||||
new() { Code = "L-03", Name = "Management Level", Description = "Engineering/Product Managers", SalaryFrom = 110000, SalaryTo = 160000, IsOverTimeEligible = false, Status = "Active" },
|
||||
new() { Code = "L-04", Name = "Senior Professional", Description = "Senior IC Roles", SalaryFrom = 95000, SalaryTo = 145000, IsOverTimeEligible = true, Status = "Active" },
|
||||
new() { Code = "L-05", Name = "Professional", Description = "Mid-Level IC Roles", SalaryFrom = 70000, SalaryTo = 100000, IsOverTimeEligible = true, Status = "Active" }
|
||||
};
|
||||
|
||||
await context.Set<Grade>().AddRangeAsync(grades);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private static async Task SeedEmployeeAsync(AppDbContext context)
|
||||
{
|
||||
if (await context.Set<Employee>().AnyAsync()) return;
|
||||
|
||||
var hqBranch = await context.Set<Branch>().FirstOrDefaultAsync(x => x.Code == "US-NYC");
|
||||
var itDept = await context.Set<Department>().FirstOrDefaultAsync(x => x.CostCenter == "US-IT");
|
||||
var managerDesig = await context.Set<Designation>().FirstOrDefaultAsync(x => x.Code == "MGR-01");
|
||||
var allGrades = await context.Set<Grade>().ToListAsync();
|
||||
|
||||
var wfhStipend = await context.Set<Income>().FirstOrDefaultAsync(x => x.Code == "WFH");
|
||||
var healthIns = await context.Set<Deduction>().FirstOrDefaultAsync(x => x.Code == "HLTH");
|
||||
var retirement401k = await context.Set<Deduction>().FirstOrDefaultAsync(x => x.Code == "401K");
|
||||
|
||||
if (hqBranch == null || itDept == null || managerDesig == null || !allGrades.Any()) return;
|
||||
|
||||
var random = new Random();
|
||||
var employees = new List<Employee>();
|
||||
var empIncomes = new List<EmployeeIncome>();
|
||||
var empDeductions = new List<EmployeeDeduction>();
|
||||
|
||||
// Nama-nama Amerika
|
||||
string[] firstNames = { "James", "Robert", "John", "Michael", "David", "William", "Richard", "Joseph", "Thomas", "Christopher", "Charles", "Daniel", "Matthew", "Anthony", "Mark", "Donald", "Steven", "Paul", "Andrew", "Joshua", "Emily", "Mary", "Patricia", "Jennifer", "Linda", "Elizabeth", "Barbara", "Susan", "Jessica", "Sarah" };
|
||||
string[] lastNames = { "Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez", "Hernandez", "Lopez", "Gonzales", "Wilson", "Anderson", "Thomas", "Taylor", "Moore", "Jackson", "Martin", "Lee", "Perez", "Thompson", "White", "Harris", "Sanchez", "Clark", "Ramirez", "Lewis", "Robinson" };
|
||||
string[] banks = { "JP Morgan Chase", "Bank of America", "Wells Fargo", "Citibank", "Capital One" };
|
||||
|
||||
for (int i = 1; i <= 30; i++)
|
||||
{
|
||||
var fName = firstNames[random.Next(firstNames.Length)];
|
||||
var lName = lastNames[random.Next(lastNames.Length)];
|
||||
var selectedGrade = allGrades[random.Next(allGrades.Count)];
|
||||
|
||||
// Perhitungan Gaji Tahunan (Annual) dibagi 12
|
||||
var salaryRange = selectedGrade.SalaryTo - selectedGrade.SalaryFrom;
|
||||
var baseAnnualSalary = selectedGrade.SalaryFrom + (decimal)(random.NextDouble() * (double)salaryRange);
|
||||
var baseSalary = Math.Round(baseAnnualSalary / 12, 0);
|
||||
|
||||
var emp = new Employee
|
||||
{
|
||||
Code = $"ACME-{2000 + i}",
|
||||
FirstName = fName,
|
||||
LastName = lName,
|
||||
JobDescription = "Specialized staff contributing to regional US operations.",
|
||||
GradeId = selectedGrade.Id,
|
||||
BasicSalary = baseSalary,
|
||||
SalaryBankName = banks[random.Next(banks.Length)],
|
||||
SalaryBankAccountName = $"{fName} {lName}",
|
||||
SalaryBankAccountNumber = $"{random.Next(100, 999)}-{random.Next(100000, 999999)}",
|
||||
PlaceOfBirth = "USA",
|
||||
DateOfBirth = new DateTime(random.Next(1975, 2000), random.Next(1, 12), random.Next(1, 28)),
|
||||
Gender = i % 2 == 0 ? "Female" : "Male",
|
||||
MaritalStatus = "Single",
|
||||
Religion = "Christian",
|
||||
IdentityNumber = $"SSN-{random.Next(100, 999)}-{random.Next(10, 99)}-{random.Next(1000, 9999)}",
|
||||
JoinedDate = new DateTime(2022, 1, 1).AddMonths(-random.Next(1, 48)),
|
||||
EmployeeStatus = "Active",
|
||||
EmploymentType = "Full-Time",
|
||||
Email = $"{fName.ToLower()}.{lName.ToLower()}@acmeglobal.com",
|
||||
BranchId = hqBranch.Id,
|
||||
DepartmentId = itDept.Id,
|
||||
DesignationId = managerDesig.Id
|
||||
};
|
||||
|
||||
employees.Add(emp);
|
||||
|
||||
if (wfhStipend != null)
|
||||
{
|
||||
empIncomes.Add(new EmployeeIncome
|
||||
{
|
||||
Employee = emp,
|
||||
IncomeId = wfhStipend.Id,
|
||||
Amount = 150, // USD 150 per month
|
||||
Description = "Remote work utilities support",
|
||||
IsActive = true
|
||||
});
|
||||
}
|
||||
|
||||
if (healthIns != null)
|
||||
{
|
||||
empDeductions.Add(new EmployeeDeduction
|
||||
{
|
||||
Employee = emp,
|
||||
DeductionId = healthIns.Id,
|
||||
Amount = 250, // USD 250 flat per month for insurance
|
||||
Description = "Employee medical premium",
|
||||
IsActive = true
|
||||
});
|
||||
}
|
||||
|
||||
if (retirement401k != null)
|
||||
{
|
||||
empDeductions.Add(new EmployeeDeduction
|
||||
{
|
||||
Employee = emp,
|
||||
DeductionId = retirement401k.Id,
|
||||
Amount = Math.Round(baseSalary * 0.05m, 0), // 5% for 401k
|
||||
Description = "401(k) retirement plan contribution",
|
||||
IsActive = true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await context.Set<Employee>().AddRangeAsync(employees);
|
||||
await context.Set<EmployeeIncome>().AddRangeAsync(empIncomes);
|
||||
await context.Set<EmployeeDeduction>().AddRangeAsync(empDeductions);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private static async Task SeedLeaveCategoryAsync(AppDbContext context)
|
||||
{
|
||||
if (await context.Set<LeaveCategory>().AnyAsync()) return;
|
||||
|
||||
var categories = new List<LeaveCategory>
|
||||
{
|
||||
new() { Code = "US-PTO", Name = "Paid Time Off (PTO)", Quota = 20, IsPaidLeave = true, Description = "Vacation and personal days", Status = "Active" },
|
||||
new() { Code = "US-SICK", Name = "Sick Leave", Quota = 10, IsPaidLeave = true, Description = "Medical leave with notification", Status = "Active" },
|
||||
new() { Code = "US-BEREAVE", Name = "Bereavement Leave", Quota = 5, IsPaidLeave = true, Description = "Loss of immediate family members", Status = "Active" },
|
||||
new() { Code = "US-JURY", Name = "Jury Duty", Quota = 0, IsPaidLeave = true, Description = "Court-mandated duty", Status = "Active" },
|
||||
new() { Code = "US-FMLA", Name = "Family Medical Leave (FMLA)", Quota = 60, IsPaidLeave = false, Description = "Long-term medical or family care", Status = "Active" }
|
||||
};
|
||||
|
||||
await context.Set<LeaveCategory>().AddRangeAsync(categories);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private static async Task SeedLeaveBalanceAsync(AppDbContext context)
|
||||
{
|
||||
if (await context.Set<LeaveBalance>().AnyAsync()) return;
|
||||
|
||||
var employees = await context.Set<Employee>().Take(15).ToListAsync();
|
||||
var ptoLeave = await context.Set<LeaveCategory>().FirstOrDefaultAsync(x => x.Code == "US-PTO");
|
||||
|
||||
if (!employees.Any() || ptoLeave == null) return;
|
||||
|
||||
var balances = new List<LeaveBalance>();
|
||||
foreach (var emp in employees)
|
||||
{
|
||||
balances.Add(new LeaveBalance
|
||||
{
|
||||
EmployeeId = emp.Id,
|
||||
LeaveCategoryId = ptoLeave.Id,
|
||||
Entitlement = ptoLeave.Quota,
|
||||
Used = 5,
|
||||
Pending = 0,
|
||||
Remaining = ptoLeave.Quota - 5,
|
||||
Year = 2026,
|
||||
ValidFrom = new DateTime(2026, 1, 1),
|
||||
ValidTo = new DateTime(2026, 12, 31)
|
||||
});
|
||||
}
|
||||
|
||||
await context.Set<LeaveBalance>().AddRangeAsync(balances);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private static async Task SeedLeaveRequestAsync(AppDbContext context)
|
||||
{
|
||||
if (await context.Set<LeaveRequest>().AnyAsync()) return;
|
||||
|
||||
var categories = await context.Set<LeaveCategory>().ToListAsync();
|
||||
var employees = await context.Set<Employee>().ToListAsync();
|
||||
|
||||
var requests = new List<LeaveRequest>();
|
||||
|
||||
var mockData = new[] {
|
||||
new { Name = "James", Cat = "US-PTO", Start = DateTime.Now.AddDays(10), End = DateTime.Now.AddDays(14), Days = 5.0, Status = "Approved", Reason = "Grand Canyon trip" },
|
||||
new { Name = "Emily", Cat = "US-SICK", Start = DateTime.Now.AddDays(-3), End = DateTime.Now.AddDays(-1), Days = 3.0, Status = "Approved", Reason = "Medical checkup" },
|
||||
new { Name = "Robert", Cat = "US-PTO", Start = DateTime.Now.AddDays(20), End = DateTime.Now.AddDays(22), Days = 3.0, Status = "Pending", Reason = "Family reunion in Texas" }
|
||||
};
|
||||
|
||||
foreach (var item in mockData)
|
||||
{
|
||||
var emp = employees.FirstOrDefault(x => x.FirstName.Contains(item.Name));
|
||||
var cat = categories.FirstOrDefault(x => x.Code == item.Cat);
|
||||
|
||||
if (emp != null && cat != null)
|
||||
{
|
||||
requests.Add(new LeaveRequest
|
||||
{
|
||||
EmployeeId = emp.Id,
|
||||
LeaveCategoryId = cat.Id,
|
||||
StartDate = item.Start,
|
||||
EndDate = item.End,
|
||||
Days = item.Days,
|
||||
Status = item.Status,
|
||||
Reason = item.Reason
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await context.Set<LeaveRequest>().AddRangeAsync(requests);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private static async Task SeedEvaluationAsync(AppDbContext context)
|
||||
{
|
||||
if (await context.Set<Evaluation>().AnyAsync()) return;
|
||||
|
||||
var allEmployees = await context.Set<Employee>().ToListAsync();
|
||||
if (allEmployees.Count < 5) return;
|
||||
|
||||
var evaluations = new List<Evaluation>();
|
||||
var random = new Random();
|
||||
|
||||
for (int i = 0; i < Math.Min(20, allEmployees.Count); i++)
|
||||
{
|
||||
var targetEmployee = allEmployees[i];
|
||||
var potentialEvaluators = allEmployees.Where(x => x.Id != targetEmployee.Id).ToList();
|
||||
var evaluator = potentialEvaluators[random.Next(potentialEvaluators.Count)];
|
||||
|
||||
evaluations.Add(new Evaluation
|
||||
{
|
||||
EmployeeId = targetEmployee.Id,
|
||||
Period = "Annual 2025 Review",
|
||||
FinalScore = (85 + random.Next(1, 15)).ToString("F1"),
|
||||
Rating = (random.Next(3, 5)).ToString(),
|
||||
EvaluatorId = evaluator.Id,
|
||||
EvaluationDate = DateTime.Now.AddDays(-random.Next(1, 60)),
|
||||
EvaluationNote = "Employee demonstrates exceptional leadership and technical proficiency.",
|
||||
Status = "Completed"
|
||||
});
|
||||
}
|
||||
|
||||
await context.Set<Evaluation>().AddRangeAsync(evaluations);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private static async Task SeedAppraisalAsync(AppDbContext context)
|
||||
{
|
||||
if (await context.Set<Appraisal>().AnyAsync()) return;
|
||||
var employees = await context.Set<Employee>().Skip(5).Take(15).ToListAsync();
|
||||
var appraisals = new List<Appraisal>();
|
||||
var ratings = new[] { "Exceeds Expectations", "Consistently High", "Outstanding" };
|
||||
|
||||
for (int i = 0; i < 15; i++)
|
||||
{
|
||||
decimal salary = employees[i].BasicSalary;
|
||||
decimal incPercent = 3 + (i % 5);
|
||||
appraisals.Add(new Appraisal
|
||||
{
|
||||
EmployeeId = employees[i].Id,
|
||||
LastRating = ratings[i % ratings.Length],
|
||||
CurrentSalary = salary,
|
||||
IncrementPercentage = incPercent,
|
||||
NewSalary = salary + (salary * incPercent / 100),
|
||||
AppraisalType = "Annual Merit Increase",
|
||||
EffectiveDate = DateTime.Now.AddMonths(1),
|
||||
AppraisalNote = "Standard US merit increase based on FY2025 performance.",
|
||||
Status = "Approved"
|
||||
});
|
||||
}
|
||||
await context.Set<Appraisal>().AddRangeAsync(appraisals);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private static async Task SeedPromotionAsync(AppDbContext context)
|
||||
{
|
||||
if (await context.Set<Promotion>().AnyAsync()) return;
|
||||
var employees = await context.Set<Employee>().Skip(2).Take(10).ToListAsync();
|
||||
var promotions = new List<Promotion>();
|
||||
var grades = new[] { "L-05", "L-04", "L-03", "L-02" };
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
promotions.Add(new Promotion
|
||||
{
|
||||
EmployeeId = employees[i].Id,
|
||||
FromGrade = grades[0],
|
||||
ToGrade = grades[1],
|
||||
EffectiveDate = DateTime.Now.AddDays(i + 10),
|
||||
PromotionNote = "Promoted to Senior level for architectural excellence.",
|
||||
Status = "Confirmed"
|
||||
});
|
||||
}
|
||||
await context.Set<Promotion>().AddRangeAsync(promotions);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private static async Task SeedTransferAsync(AppDbContext context)
|
||||
{
|
||||
if (await context.Set<Transfer>().AnyAsync()) return;
|
||||
var employees = await context.Set<Employee>().Take(10).ToListAsync();
|
||||
var branches = await context.Set<Branch>().ToListAsync();
|
||||
var depts = await context.Set<Department>().ToListAsync();
|
||||
var desigs = await context.Set<Designation>().ToListAsync();
|
||||
var transfers = new List<Transfer>();
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
transfers.Add(new Transfer
|
||||
{
|
||||
EmployeeId = employees[i].Id,
|
||||
FromBranchId = branches[0].Id,
|
||||
ToBranchId = branches[1].Id,
|
||||
FromDepartmentId = depts[0].Id,
|
||||
ToDepartmentId = depts[0].Id,
|
||||
FromDesignationId = desigs[4].Id,
|
||||
ToDesignationId = desigs[3].Id,
|
||||
TransferDate = DateTime.Now.AddDays(i + 5),
|
||||
TransferNote = "Relocation to SFO for Cloud Project acceleration.",
|
||||
Status = "Active"
|
||||
});
|
||||
}
|
||||
await context.Set<Transfer>().AddRangeAsync(transfers);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private static async Task SeedPayrollAsync(AppDbContext context)
|
||||
{
|
||||
if (await context.PayrollProcess.AnyAsync()) return;
|
||||
|
||||
var employees = await context.Employee
|
||||
.Include(x => x.EmployeeIncome)
|
||||
.Include(x => x.EmployeeDeduction)
|
||||
.ToListAsync();
|
||||
|
||||
if (!employees.Any()) return;
|
||||
|
||||
var payrollProcesses = new List<PayrollProcess>();
|
||||
var payrollDetails = new List<PayrollDetail>();
|
||||
var payrollComponents = new List<PayrollComponent>();
|
||||
|
||||
for (int m = 2; m >= 0; m--)
|
||||
{
|
||||
var date = DateTime.Now.AddMonths(-m);
|
||||
var periodName = date.ToString("MMMM yyyy");
|
||||
var fromDate = new DateTime(date.Year, date.Month, 1);
|
||||
var toDate = fromDate.AddMonths(1).AddDays(-1);
|
||||
|
||||
var process = new PayrollProcess
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
AutoNumber = $"US-PAY-{date:yyyyMM}-{3 - m:D3}",
|
||||
PeriodName = periodName,
|
||||
FromDate = fromDate,
|
||||
ToDate = toDate,
|
||||
Status = "Paid",
|
||||
ProcessedBy = "US Payroll Admin",
|
||||
ProcessedAt = DateTime.Now.AddMonths(-m),
|
||||
Description = $"Global US Payroll processing for {periodName}"
|
||||
};
|
||||
|
||||
decimal grandBasic = 0, grandIncome = 0, grandDeduction = 0;
|
||||
|
||||
foreach (var emp in employees)
|
||||
{
|
||||
var totalInc = emp.EmployeeIncome?.Sum(x => x.Amount) ?? 0;
|
||||
var totalDed = emp.EmployeeDeduction?.Sum(x => x.Amount) ?? 0;
|
||||
var thp = emp.BasicSalary + totalInc - totalDed;
|
||||
|
||||
var detail = new PayrollDetail
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
PayrollProcessId = process.Id,
|
||||
EmployeeId = emp.Id,
|
||||
EmployeeCode = emp.Code,
|
||||
EmployeeName = $"{emp.FirstName} {emp.LastName}",
|
||||
BasicSalary = emp.BasicSalary,
|
||||
TotalIncome = totalInc,
|
||||
TotalDeduction = totalDed,
|
||||
TakeHomePay = thp
|
||||
};
|
||||
|
||||
if (emp.EmployeeIncome != null)
|
||||
{
|
||||
foreach (var inc in emp.EmployeeIncome)
|
||||
{
|
||||
payrollComponents.Add(new PayrollComponent
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
PayrollDetailId = detail.Id,
|
||||
ComponentName = inc.Income?.Name,
|
||||
ComponentType = "Income",
|
||||
Amount = inc.Amount,
|
||||
Description = inc.Description
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (emp.EmployeeDeduction != null)
|
||||
{
|
||||
foreach (var ded in emp.EmployeeDeduction)
|
||||
{
|
||||
payrollComponents.Add(new PayrollComponent
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
PayrollDetailId = detail.Id,
|
||||
ComponentName = ded.Deduction?.Name,
|
||||
ComponentType = "Deduction",
|
||||
Amount = ded.Amount,
|
||||
Description = ded.Description
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
grandBasic += detail.BasicSalary;
|
||||
grandIncome += totalInc;
|
||||
grandDeduction += totalDed;
|
||||
payrollDetails.Add(detail);
|
||||
}
|
||||
|
||||
process.TotalEmployees = employees.Count;
|
||||
process.TotalBasicSalary = grandBasic;
|
||||
process.TotalIncome = grandIncome;
|
||||
process.TotalDeduction = grandDeduction;
|
||||
process.TotalGross = grandBasic + grandIncome;
|
||||
process.TotalTakeHomePay = process.TotalGross - grandDeduction;
|
||||
|
||||
payrollProcesses.Add(process);
|
||||
}
|
||||
|
||||
await context.PayrollProcess.AddRangeAsync(payrollProcesses);
|
||||
await context.PayrollDetail.AddRangeAsync(payrollDetails);
|
||||
await context.PayrollComponent.AddRangeAsync(payrollComponents);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Indotalent.Infrastructure.Database;
|
||||
|
||||
public class DatabaseService(IOptions<DatabaseSettingsModel> options)
|
||||
{
|
||||
private readonly DatabaseSettingsModel _settings = options.Value;
|
||||
|
||||
public DatabaseSettingsModel GetConfiguration() => _settings;
|
||||
|
||||
public DatabaseProviderModel GetActiveProvider()
|
||||
{
|
||||
if (_settings.MsSQL.IsUsed) return _settings.MsSQL;
|
||||
if (_settings.MySQL.IsUsed) return _settings.MySQL;
|
||||
if (_settings.PostgreSQL.IsUsed) return _settings.PostgreSQL;
|
||||
return new DatabaseProviderModel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Indotalent.Infrastructure.Database;
|
||||
|
||||
public class DatabaseSettingsModel
|
||||
{
|
||||
public DatabaseProviderModel MsSQL { get; set; } = new();
|
||||
public DatabaseProviderModel MySQL { get; set; } = new();
|
||||
public DatabaseProviderModel PostgreSQL { get; set; } = new();
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Shared.Consts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Indotalent.Infrastructure.Database.MsSQL.Configuration;
|
||||
|
||||
public class ApplicationUserConfiguration : IEntityTypeConfiguration<ApplicationUser>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ApplicationUser> builder)
|
||||
{
|
||||
builder.Property(e => e.FullName).HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
builder.Property(e => e.SsoIdFirebase).HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
builder.Property(e => e.SsoIdKeycloak).HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
builder.Property(e => e.SsoIdAzure).HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
builder.Property(e => e.SsoIdAws).HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
builder.Property(e => e.SsoIdOther1).HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
builder.Property(e => e.SsoIdOther2).HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
builder.Property(e => e.SsoIdOther3).HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
builder.Property(e => e.AvatarFile).HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
builder.Property(e => e.RefreshToken).HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
|
||||
builder.Property(e => e.FirstName).HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
builder.Property(e => e.LastName).HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
builder.Property(e => e.ShortBio).HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
builder.Property(e => e.JobTitle).HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.StreetAddress)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.City)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.StateProvince)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.ZipCode)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Country)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.SocialMediaLinkedIn)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.SocialMediaX)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.SocialMediaFacebook)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.SocialMediaInstagram)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.SocialMediaTikTok)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.OtherInformation1)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.OtherInformation2)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.OtherInformation3)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Shared.Consts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Indotalent.Infrastructure.Database.MsSQL.Configuration;
|
||||
|
||||
public class AppraisalConfiguration : IEntityTypeConfiguration<Appraisal>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Appraisal> builder)
|
||||
{
|
||||
builder.Property(a => a.AutoNumber).HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
builder.Property(a => a.EmployeeId).HasMaxLength(GlobalConsts.StringLengthId);
|
||||
builder.Property(a => a.LastRating).HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
builder.Property(a => a.AppraisalType).HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
builder.Property(a => a.AppraisalNote).HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
builder.Property(a => a.Status).HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(a => a.CurrentSalary).HasColumnType("decimal(18,2)");
|
||||
builder.Property(a => a.IncrementPercentage).HasColumnType("decimal(18,2)");
|
||||
builder.Property(a => a.NewSalary).HasColumnType("decimal(18,2)");
|
||||
|
||||
builder.HasIndex(e => new { e.TenantId, e.AutoNumber }).IsUnique();
|
||||
builder.HasIndex(a => a.EmployeeId);
|
||||
|
||||
builder.HasOne(a => a.Employee)
|
||||
.WithMany()
|
||||
.HasForeignKey(a => a.EmployeeId)
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Shared.Consts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Indotalent.Infrastructure.Database.MsSQL.Configuration;
|
||||
|
||||
public class AutoNumberSequenceConfiguration : IEntityTypeConfiguration<AutoNumberSequence>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<AutoNumberSequence> builder)
|
||||
{
|
||||
|
||||
builder.Property(e => e.EntityName)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.PrefixTemplate)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.SuffixTemplate)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.HasIndex(e => new { e.TenantId, e.EntityName, e.Year, e.Month }).IsUnique();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Shared.Consts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Indotalent.Infrastructure.Database.MsSQL.Configuration;
|
||||
|
||||
public class BranchConfiguration : IEntityTypeConfiguration<Branch>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Branch> builder)
|
||||
{
|
||||
builder.Property(e => e.AutoNumber)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Code)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Name)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Description)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.StreetAddress)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.City)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.StateProvince)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.ZipCode)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Phone)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Email)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.OtherInformation1)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.OtherInformation2)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.OtherInformation3)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.HasIndex(e => new { e.TenantId, e.AutoNumber }).IsUnique();
|
||||
builder.HasIndex(e => new { e.TenantId, e.Code }).IsUnique();
|
||||
builder.HasIndex(e => e.Name);
|
||||
builder.HasIndex(e => e.City);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Shared.Consts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Indotalent.Infrastructure.Database.MsSQL.Configuration;
|
||||
|
||||
public class CompanyConfiguration : IEntityTypeConfiguration<Company>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Company> builder)
|
||||
{
|
||||
|
||||
builder.Property(e => e.AutoNumber)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Name)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Description)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.CurrencyId)
|
||||
.HasMaxLength(GlobalConsts.StringLengthId);
|
||||
|
||||
builder.HasOne(e => e.Currency)
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.CurrencyId)
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
|
||||
builder.Property(e => e.TaxIdentification)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.BusinessLicense)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.CompanyLogo)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.StreetAddress)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.City)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.StateProvince)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.ZipCode)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Phone)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Email)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.SocialMediaLinkedIn)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.SocialMediaX)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.SocialMediaFacebook)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.SocialMediaInstagram)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.SocialMediaTikTok)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.OtherInformation1)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.OtherInformation2)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.OtherInformation3)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
|
||||
builder.HasIndex(e => new { e.TenantId, e.AutoNumber }).IsUnique();
|
||||
builder.HasIndex(e => e.Name);
|
||||
builder.HasIndex(e => e.Email);
|
||||
builder.HasIndex(e => e.CurrencyId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Shared.Consts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Indotalent.Infrastructure.Database.MsSQL.Configuration;
|
||||
|
||||
public class CurrencyConfiguration : IEntityTypeConfiguration<Currency>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Currency> builder)
|
||||
{
|
||||
|
||||
builder.Property(e => e.AutoNumber)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Code)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Name)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Symbol)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.CountryOwner)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Description)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
|
||||
builder.HasIndex(e => new { e.TenantId, e.AutoNumber }).IsUnique();
|
||||
builder.HasIndex(e => new { e.TenantId, e.Code }).IsUnique();
|
||||
builder.HasIndex(e => e.Name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Shared.Consts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Indotalent.Infrastructure.Database.MsSQL.Configuration;
|
||||
|
||||
public class DeductionConfiguration : IEntityTypeConfiguration<Deduction>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Deduction> builder)
|
||||
{
|
||||
builder.Property(e => e.AutoNumber)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Code)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Name)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Category)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.CalculationMethod)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Status)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Description)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.HasIndex(e => new { e.TenantId, e.AutoNumber }).IsUnique();
|
||||
builder.HasIndex(e => new { e.TenantId, e.Code }).IsUnique();
|
||||
builder.HasIndex(e => e.Name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Shared.Consts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Indotalent.Infrastructure.Database.MsSQL.Configuration;
|
||||
|
||||
public class DepartmentConfiguration : IEntityTypeConfiguration<Department>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Department> builder)
|
||||
{
|
||||
builder.Property(e => e.AutoNumber)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.CostCenter)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Name)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Description)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.HeadOfDeptartment)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.OtherInformation1)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.OtherInformation2)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.OtherInformation3)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.HasIndex(e => new { e.TenantId, e.AutoNumber }).IsUnique();
|
||||
builder.HasIndex(e => new { e.TenantId, e.CostCenter }).IsUnique();
|
||||
builder.HasIndex(e => e.Name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Shared.Consts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Indotalent.Infrastructure.Database.MsSQL.Configuration;
|
||||
|
||||
public class DesignationConfiguration : IEntityTypeConfiguration<Designation>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Designation> builder)
|
||||
{
|
||||
builder.Property(e => e.AutoNumber)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Code)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Name)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Description)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.Level)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.OtherInformation1)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.OtherInformation2)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.OtherInformation3)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.HasIndex(e => new { e.TenantId, e.AutoNumber }).IsUnique();
|
||||
builder.HasIndex(e => new { e.TenantId, e.Code }).IsUnique();
|
||||
builder.HasIndex(e => e.Name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Shared.Consts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Indotalent.Infrastructure.Database.MsSQL.Configuration;
|
||||
|
||||
public class EmployeeConfiguration : IEntityTypeConfiguration<Employee>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Employee> builder)
|
||||
{
|
||||
builder.Property(e => e.AutoNumber)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Code)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.FirstName)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.MiddleName)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.LastName)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.JobDescription)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.GradeId)
|
||||
.HasMaxLength(GlobalConsts.StringLengthId);
|
||||
|
||||
builder.Property(e => e.BranchId)
|
||||
.HasMaxLength(GlobalConsts.StringLengthId);
|
||||
|
||||
builder.Property(e => e.DepartmentId)
|
||||
.HasMaxLength(GlobalConsts.StringLengthId);
|
||||
|
||||
builder.Property(e => e.DesignationId)
|
||||
.HasMaxLength(GlobalConsts.StringLengthId);
|
||||
|
||||
builder.Property(e => e.BasicSalary)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
builder.Property(e => e.SalaryBankName)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.SalaryBankAccountName)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.SalaryBankAccountNumber)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.PlaceOfBirth)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Gender)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.MaritalStatus)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Religion)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.BloodType)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.IdentityNumber)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.TaxNumber)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.LastEducation)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.EmployeeStatus)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.EmploymentType)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.StreetAddress)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.City)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.StateProvince)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.ZipCode)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Phone)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Email)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.SocialMediaLinkedIn)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.SocialMediaX)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.SocialMediaFacebook)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.SocialMediaInstagram)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.SocialMediaTikTok)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.OtherInformation1)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.OtherInformation2)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.OtherInformation3)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.HasOne(e => e.Grade)
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.GradeId)
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
|
||||
builder.HasOne(e => e.Branch)
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.BranchId)
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
|
||||
builder.HasOne(e => e.Department)
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.DepartmentId)
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
|
||||
builder.HasOne(e => e.Designation)
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.DesignationId)
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
|
||||
builder.HasMany(e => e.EmployeeIncome)
|
||||
.WithOne(ei => ei.Employee)
|
||||
.HasForeignKey(ei => ei.EmployeeId)
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
|
||||
builder.HasMany(e => e.EmployeeDeduction)
|
||||
.WithOne(ed => ed.Employee)
|
||||
.HasForeignKey(ed => ed.EmployeeId)
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
|
||||
builder.HasIndex(e => new { e.TenantId, e.AutoNumber }).IsUnique();
|
||||
builder.HasIndex(e => new { e.TenantId, e.Code }).IsUnique();
|
||||
builder.HasIndex(e => e.FirstName);
|
||||
builder.HasIndex(e => e.LastName);
|
||||
builder.HasIndex(e => e.IdentityNumber);
|
||||
builder.HasIndex(e => e.Email);
|
||||
builder.HasIndex(e => e.GradeId);
|
||||
builder.HasIndex(e => e.BranchId);
|
||||
builder.HasIndex(e => e.DepartmentId);
|
||||
builder.HasIndex(e => e.DesignationId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Shared.Consts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Indotalent.Infrastructure.Database.MsSQL.Configuration;
|
||||
|
||||
public class EmployeeDeductionConfiguration : IEntityTypeConfiguration<EmployeeDeduction>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<EmployeeDeduction> builder)
|
||||
{
|
||||
builder.Property(e => e.AutoNumber)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.EmployeeId)
|
||||
.HasMaxLength(GlobalConsts.StringLengthId);
|
||||
|
||||
builder.Property(e => e.DeductionId)
|
||||
.HasMaxLength(GlobalConsts.StringLengthId);
|
||||
|
||||
builder.Property(e => e.Description)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Amount)
|
||||
.HasPrecision(18, 2);
|
||||
|
||||
builder.HasIndex(e => new { e.TenantId, e.AutoNumber }).IsUnique();
|
||||
builder.HasIndex(e => e.EmployeeId);
|
||||
builder.HasIndex(e => e.DeductionId);
|
||||
|
||||
builder.HasOne(e => e.Employee)
|
||||
.WithMany(p => p.EmployeeDeduction)
|
||||
.HasForeignKey(e => e.EmployeeId)
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
|
||||
builder.HasOne(e => e.Deduction)
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.DeductionId)
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Shared.Consts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Indotalent.Infrastructure.Database.MsSQL.Configuration;
|
||||
|
||||
public class EmployeeIncomeConfiguration : IEntityTypeConfiguration<EmployeeIncome>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<EmployeeIncome> builder)
|
||||
{
|
||||
builder.Property(e => e.AutoNumber)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.EmployeeId)
|
||||
.HasMaxLength(GlobalConsts.StringLengthId);
|
||||
|
||||
builder.Property(e => e.IncomeId)
|
||||
.HasMaxLength(GlobalConsts.StringLengthId);
|
||||
|
||||
builder.Property(e => e.Description)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Amount)
|
||||
.HasPrecision(18, 2);
|
||||
|
||||
builder.HasIndex(e => new { e.TenantId, e.AutoNumber }).IsUnique();
|
||||
builder.HasIndex(e => e.EmployeeId);
|
||||
builder.HasIndex(e => e.IncomeId);
|
||||
|
||||
builder.HasOne(e => e.Employee)
|
||||
.WithMany(p => p.EmployeeIncome)
|
||||
.HasForeignKey(e => e.EmployeeId)
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
|
||||
builder.HasOne(e => e.Income)
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.IncomeId)
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Shared.Consts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Indotalent.Infrastructure.Database.MsSQL.Configuration;
|
||||
|
||||
public class EvaluationConfiguration : IEntityTypeConfiguration<Evaluation>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Evaluation> builder)
|
||||
{
|
||||
builder.Property(e => e.AutoNumber).HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
builder.Property(e => e.EmployeeId).HasMaxLength(GlobalConsts.StringLengthId);
|
||||
builder.Property(e => e.EvaluatorId).HasMaxLength(GlobalConsts.StringLengthId);
|
||||
builder.Property(e => e.Period).HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
builder.Property(e => e.FinalScore).HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
builder.Property(e => e.Rating).HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
builder.Property(e => e.EvaluationNote).HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
builder.Property(e => e.Status).HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.HasIndex(e => new { e.TenantId, e.AutoNumber }).IsUnique();
|
||||
builder.HasIndex(e => e.EmployeeId);
|
||||
builder.HasIndex(e => e.EvaluatorId);
|
||||
|
||||
builder.HasOne(e => e.Employee)
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.EmployeeId)
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
|
||||
builder.HasOne(e => e.Evaluator)
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.EvaluatorId)
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Shared.Consts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Indotalent.Infrastructure.Database.MsSQL.Configuration;
|
||||
|
||||
public class GradeConfiguration : IEntityTypeConfiguration<Grade>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Grade> builder)
|
||||
{
|
||||
builder.Property(e => e.AutoNumber)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Code)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Name)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Status)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Description)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.SalaryFrom)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
builder.Property(e => e.SalaryTo)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
builder.HasIndex(e => new { e.TenantId, e.AutoNumber }).IsUnique();
|
||||
builder.HasIndex(e => new { e.TenantId, e.Code }).IsUnique();
|
||||
builder.HasIndex(e => e.Name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Shared.Consts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Indotalent.Infrastructure.Database.MsSQL.Configuration;
|
||||
|
||||
public class IncomeConfiguration : IEntityTypeConfiguration<Income>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Income> builder)
|
||||
{
|
||||
builder.Property(e => e.AutoNumber)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Code)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Name)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Type)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.CalculationBase)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Status)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Description)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.HasIndex(e => new { e.TenantId, e.AutoNumber }).IsUnique();
|
||||
builder.HasIndex(e => new { e.TenantId, e.Code }).IsUnique();
|
||||
builder.HasIndex(e => e.Name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Shared.Consts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Indotalent.Infrastructure.Database.MsSQL.Configuration;
|
||||
|
||||
public class LeaveBalanceConfiguration : IEntityTypeConfiguration<LeaveBalance>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<LeaveBalance> builder)
|
||||
{
|
||||
builder.Property(e => e.AutoNumber)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.HasOne(e => e.Employee)
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.EmployeeId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.HasOne(e => e.LeaveCategory)
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.LeaveCategoryId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.HasIndex(e => new { e.TenantId, e.AutoNumber }).IsUnique();
|
||||
builder.HasIndex(e => e.Year);
|
||||
|
||||
builder.HasIndex(e => new { e.TenantId, e.EmployeeId, e.LeaveCategoryId, e.Year }).IsUnique();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Shared.Consts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Indotalent.Infrastructure.Database.MsSQL.Configuration;
|
||||
|
||||
public class LeaveCategoryConfiguration : IEntityTypeConfiguration<LeaveCategory>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<LeaveCategory> builder)
|
||||
{
|
||||
builder.Property(e => e.AutoNumber)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Code)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Name)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Description)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.Status)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.HasIndex(e => new { e.TenantId, e.AutoNumber }).IsUnique();
|
||||
builder.HasIndex(e => new { e.TenantId, e.Code }).IsUnique();
|
||||
builder.HasIndex(e => e.Name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Shared.Consts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Indotalent.Infrastructure.Database.MsSQL.Configuration;
|
||||
|
||||
public class LeaveRequestConfiguration : IEntityTypeConfiguration<LeaveRequest>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<LeaveRequest> builder)
|
||||
{
|
||||
builder.Property(e => e.AutoNumber)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Reason)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.Status)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.AttachmentPath)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.Property(e => e.ApproverId)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.RejectReason)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
builder.HasOne(e => e.Employee)
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.EmployeeId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.HasOne(e => e.LeaveCategory)
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.LeaveCategoryId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.HasIndex(e => new { e.TenantId, e.AutoNumber }).IsUnique();
|
||||
builder.HasIndex(e => e.Status);
|
||||
builder.HasIndex(e => e.StartDate);
|
||||
builder.HasIndex(e => e.EndDate);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Shared.Consts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Indotalent.Infrastructure.Database.MsSQL.Configuration;
|
||||
|
||||
public class PayrollComponentConfiguration : IEntityTypeConfiguration<PayrollComponent>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<PayrollComponent> builder)
|
||||
{
|
||||
|
||||
builder.Property(e => e.PayrollDetailId)
|
||||
.HasMaxLength(GlobalConsts.StringLengthId);
|
||||
|
||||
builder.Property(e => e.ComponentName)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.ComponentType)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Description)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.HasOne(e => e.PayrollDetail)
|
||||
.WithMany(d => d.Components)
|
||||
.HasForeignKey(e => e.PayrollDetailId)
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
|
||||
builder.HasIndex(e => e.ComponentName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Shared.Consts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Indotalent.Infrastructure.Database.MsSQL.Configuration;
|
||||
|
||||
public class PayrollDetailConfiguration : IEntityTypeConfiguration<PayrollDetail>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<PayrollDetail> builder)
|
||||
{
|
||||
builder.Property(e => e.PayrollProcessId)
|
||||
.HasMaxLength(GlobalConsts.StringLengthId);
|
||||
|
||||
builder.Property(e => e.EmployeeId)
|
||||
.HasMaxLength(GlobalConsts.StringLengthId);
|
||||
|
||||
builder.Property(e => e.EmployeeCode)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.EmployeeName)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.HasOne(e => e.PayrollProcess)
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.PayrollProcessId)
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
|
||||
builder.HasOne(e => e.Employee)
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.EmployeeId)
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
|
||||
builder.HasIndex(e => e.EmployeeCode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Shared.Consts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Indotalent.Infrastructure.Database.MsSQL.Configuration;
|
||||
|
||||
public class PayrollProcessConfiguration : IEntityTypeConfiguration<PayrollProcess>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<PayrollProcess> builder)
|
||||
{
|
||||
builder.Property(e => e.AutoNumber)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.PeriodName)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Status)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.ProcessedBy)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Description)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.HasIndex(e => new { e.TenantId, e.AutoNumber }).IsUnique();
|
||||
builder.HasIndex(e => e.PeriodName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Shared.Consts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Indotalent.Infrastructure.Database.MsSQL.Configuration;
|
||||
|
||||
public class PromotionConfiguration : IEntityTypeConfiguration<Promotion>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Promotion> builder)
|
||||
{
|
||||
builder.Property(p => p.AutoNumber).HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
builder.Property(p => p.EmployeeId).HasMaxLength(GlobalConsts.StringLengthId);
|
||||
builder.Property(p => p.FromGrade).HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
builder.Property(p => p.ToGrade).HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
builder.Property(p => p.PromotionNote).HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
builder.Property(p => p.Status).HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.HasIndex(e => new { e.TenantId, e.AutoNumber }).IsUnique();
|
||||
builder.HasIndex(p => p.EmployeeId);
|
||||
|
||||
builder.HasOne(p => p.Employee)
|
||||
.WithMany()
|
||||
.HasForeignKey(p => p.EmployeeId)
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Indotalent.Infrastructure.Database.MsSQL.Configuration;
|
||||
|
||||
public class LogConfiguration : IEntityTypeConfiguration<SerilogLogs>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<SerilogLogs> builder)
|
||||
{
|
||||
builder.ToTable("SerilogLogs");
|
||||
|
||||
builder.HasKey(e => e.Id);
|
||||
|
||||
builder.Property(e => e.Message)
|
||||
.HasMaxLength(2000);
|
||||
|
||||
builder.Property(e => e.Level)
|
||||
.HasMaxLength(128);
|
||||
|
||||
builder.Property(e => e.TimeStamp)
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(e => e.Exception)
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
builder.Property(e => e.Properties)
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
builder.Property(e => e.MessageTemplate)
|
||||
.HasMaxLength(2000);
|
||||
|
||||
builder.Property(e => e.LogEvent)
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
builder.HasIndex(e => e.TimeStamp)
|
||||
.HasDatabaseName("IX_SerilogLogs_TimeStamp");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Shared.Consts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Indotalent.Infrastructure.Database.MsSQL.Configuration;
|
||||
|
||||
public class TaxConfiguration : IEntityTypeConfiguration<Tax>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Tax> builder)
|
||||
{
|
||||
|
||||
builder.Property(e => e.AutoNumber)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Code)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Name)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.PercentageValue)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
builder.Property(e => e.Category)
|
||||
.HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.Property(e => e.Description)
|
||||
.HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
|
||||
|
||||
builder.HasIndex(e => new { e.TenantId, e.AutoNumber }).IsUnique();
|
||||
builder.HasIndex(e => new { e.TenantId, e.Code }).IsUnique();
|
||||
builder.HasIndex(e => e.Name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Shared.Consts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Indotalent.Infrastructure.Database.MsSQL.Configuration;
|
||||
|
||||
public class TransferConfiguration : IEntityTypeConfiguration<Transfer>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Transfer> builder)
|
||||
{
|
||||
builder.Property(t => t.AutoNumber).HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
builder.Property(t => t.EmployeeId).HasMaxLength(GlobalConsts.StringLengthId);
|
||||
|
||||
builder.Property(t => t.FromBranchId).HasMaxLength(GlobalConsts.StringLengthId);
|
||||
builder.Property(t => t.FromDepartmentId).HasMaxLength(GlobalConsts.StringLengthId);
|
||||
builder.Property(t => t.FromDesignationId).HasMaxLength(GlobalConsts.StringLengthId);
|
||||
|
||||
builder.Property(t => t.ToBranchId).HasMaxLength(GlobalConsts.StringLengthId);
|
||||
builder.Property(t => t.ToDepartmentId).HasMaxLength(GlobalConsts.StringLengthId);
|
||||
builder.Property(t => t.ToDesignationId).HasMaxLength(GlobalConsts.StringLengthId);
|
||||
|
||||
builder.Property(t => t.TransferNote).HasMaxLength(GlobalConsts.StringLengthMedium);
|
||||
builder.Property(t => t.Status).HasMaxLength(GlobalConsts.StringLengthShort);
|
||||
|
||||
builder.HasIndex(e => new { e.TenantId, e.AutoNumber }).IsUnique();
|
||||
builder.HasIndex(t => t.EmployeeId);
|
||||
|
||||
builder.HasOne(t => t.Employee).WithMany().HasForeignKey(t => t.EmployeeId).OnDelete(DeleteBehavior.NoAction);
|
||||
|
||||
builder.HasOne(t => t.FromBranch).WithMany().HasForeignKey(t => t.FromBranchId).OnDelete(DeleteBehavior.NoAction);
|
||||
builder.HasOne(t => t.FromDepartment).WithMany().HasForeignKey(t => t.FromDepartmentId).OnDelete(DeleteBehavior.NoAction);
|
||||
builder.HasOne(t => t.FromDesignation).WithMany().HasForeignKey(t => t.FromDesignationId).OnDelete(DeleteBehavior.NoAction);
|
||||
|
||||
builder.HasOne(t => t.ToBranch).WithMany().HasForeignKey(t => t.ToBranchId).OnDelete(DeleteBehavior.NoAction);
|
||||
builder.HasOne(t => t.ToDepartment).WithMany().HasForeignKey(t => t.ToDepartmentId).OnDelete(DeleteBehavior.NoAction);
|
||||
builder.HasOne(t => t.ToDesignation).WithMany().HasForeignKey(t => t.ToDesignationId).OnDelete(DeleteBehavior.NoAction);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace Indotalent.Infrastructure.Database.MsSQL;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
public static class MsSQLConfiguration
|
||||
{
|
||||
public static IServiceCollection AddMsSQLContext<TContext>(this IServiceCollection services, string connectionString)
|
||||
where TContext : DbContext
|
||||
{
|
||||
services.AddDbContext<TContext>(options =>
|
||||
options.UseSqlServer(connectionString, m =>
|
||||
m.MigrationsAssembly(typeof(TContext).Assembly.FullName)));
|
||||
return services;
|
||||
}
|
||||
|
||||
public static void InitializeDatabase<TContext>(IServiceProvider serviceProvider) where TContext : DbContext
|
||||
{
|
||||
using var scope = serviceProvider.CreateScope();
|
||||
var context = scope.ServiceProvider.GetRequiredService<TContext>();
|
||||
context.Database.EnsureCreated();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace Indotalent.Infrastructure.Database.MySQL;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
public static class MySQLConfiguration
|
||||
{
|
||||
public static IServiceCollection AddMySQLContext<TContext>(this IServiceCollection services, string connectionString)
|
||||
where TContext : DbContext
|
||||
{
|
||||
services.AddDbContext<TContext>(options =>
|
||||
options.UseMySQL(connectionString, m =>
|
||||
m.MigrationsAssembly(typeof(TContext).Assembly.FullName)));
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace Indotalent.Infrastructure.Database.PostgreSQL;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
public static class PostgreSQLConfiguration
|
||||
{
|
||||
public static IServiceCollection AddPostgreSQLContext<TContext>(this IServiceCollection services, string connectionString)
|
||||
where TContext : DbContext
|
||||
{
|
||||
services.AddDbContext<TContext>(options =>
|
||||
options.UseNpgsql(connectionString, m =>
|
||||
m.MigrationsAssembly(typeof(TContext).Assembly.FullName)));
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
|
||||
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Infrastructure.Email.Mailgun;
|
||||
using Indotalent.Infrastructure.Email.Mailjet;
|
||||
using Indotalent.Infrastructure.Email.SendGrid;
|
||||
using Indotalent.Infrastructure.Email.Smtp;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Indotalent.Infrastructure.Email;
|
||||
|
||||
public static class DI
|
||||
{
|
||||
public static IServiceCollection AddEmailService(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddScoped<EmailService>();
|
||||
|
||||
|
||||
var emailSettings = configuration.GetSection("EmailSettings").Get<EmailSettingsModel>() ?? new EmailSettingsModel();
|
||||
|
||||
if (emailSettings.SendGrid?.IsUsed == true)
|
||||
{
|
||||
services.AddHttpClient<IEmailSender, SendGridEmailSender>();
|
||||
}
|
||||
else if (emailSettings.Mailgun?.IsUsed == true)
|
||||
{
|
||||
services.AddHttpClient<IEmailSender, MailgunEmailSender>();
|
||||
}
|
||||
else if (emailSettings.Mailjet?.IsUsed == true)
|
||||
{
|
||||
services.AddHttpClient<IEmailSender, MailjetEmailSender>();
|
||||
}
|
||||
else if (emailSettings.Smtp?.IsUsed == true)
|
||||
{
|
||||
services.AddTransient<IEmailSender, SmtpEmailSender>();
|
||||
}
|
||||
else
|
||||
{
|
||||
services.AddScoped<IEmailSender, DefaultEmailSender>();
|
||||
}
|
||||
|
||||
services.AddTransient<IEmailSender<ApplicationUser>, IdentityEmailManager<ApplicationUser>>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace Indotalent.Infrastructure.Email;
|
||||
|
||||
public class DefaultEmailSender : IEmailSender
|
||||
{
|
||||
private readonly ILogger<DefaultEmailSender> _logger;
|
||||
|
||||
public DefaultEmailSender(ILogger<DefaultEmailSender> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Task SendEmailAsync(string email, string subject, string htmlMessage)
|
||||
{
|
||||
_logger.LogWarning("Email sending requested but NO active email provider found in EmailSettings.");
|
||||
_logger.LogInformation("To: {Email}", email);
|
||||
_logger.LogInformation("Subject: {Subject}", subject);
|
||||
_logger.LogInformation("Content: {Message}", htmlMessage);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Indotalent.Infrastructure.Email;
|
||||
|
||||
public class EmailService(IOptions<EmailSettingsModel> options)
|
||||
{
|
||||
private readonly EmailSettingsModel _settings = options.Value;
|
||||
|
||||
public EmailSettingsModel GetConfiguration() => _settings;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Indotalent.Infrastructure.Email.Mailgun;
|
||||
using Indotalent.Infrastructure.Email.Mailjet;
|
||||
using Indotalent.Infrastructure.Email.SendGrid;
|
||||
using Indotalent.Infrastructure.Email.Smtp;
|
||||
|
||||
namespace Indotalent.Infrastructure.Email;
|
||||
|
||||
public class EmailSettingsModel
|
||||
{
|
||||
public SendGridSettingsModel SendGrid { get; set; } = new();
|
||||
public MailgunSettingsModel Mailgun { get; set; } = new();
|
||||
public MailjetSettingsModel Mailjet { get; set; } = new();
|
||||
public SmtpSettingsModel Smtp { get; set; } = new();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Indotalent.Infrastructure.Email;
|
||||
|
||||
public interface IEmailSender
|
||||
{
|
||||
Task SendEmailAsync(string email, string subject, string htmlMessage);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Indotalent.Infrastructure.Email;
|
||||
|
||||
public class IdentityEmailManager<TUser> : IEmailSender<TUser> where TUser : class
|
||||
{
|
||||
private readonly IEmailSender _emailSender;
|
||||
|
||||
public IdentityEmailManager(IEmailSender emailSender)
|
||||
{
|
||||
_emailSender = emailSender;
|
||||
}
|
||||
|
||||
public Task SendConfirmationLinkAsync(TUser user, string email, string confirmationLink)
|
||||
{
|
||||
var message = confirmationLink.Contains("<")
|
||||
? confirmationLink
|
||||
: $"Please confirm your account by clicking this link: <a href='{confirmationLink}'>Confirm Account</a>";
|
||||
|
||||
return _emailSender.SendEmailAsync(email, "Account Confirmation", message);
|
||||
}
|
||||
|
||||
public Task SendPasswordResetLinkAsync(TUser user, string email, string resetLink)
|
||||
{
|
||||
var message = resetLink.Contains("<")
|
||||
? resetLink
|
||||
: $"To reset your password, please click the following link: <a href='{resetLink}'>Reset Password</a>";
|
||||
|
||||
return _emailSender.SendEmailAsync(email, "Password Reset Request", message);
|
||||
}
|
||||
|
||||
public Task SendPasswordResetCodeAsync(TUser user, string email, string resetCode) =>
|
||||
_emailSender.SendEmailAsync(email, "Your Password Reset Code",
|
||||
$"Your password reset security code is: <b>{resetCode}</b>. Please enter this code to proceed with the reset process.");
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
|
||||
namespace Indotalent.Infrastructure.Email.Mailgun;
|
||||
|
||||
public class MailgunEmailSender : IEmailSender
|
||||
{
|
||||
private readonly MailgunSettingsModel _settings;
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public MailgunEmailSender(IOptions<EmailSettingsModel> emailOptions, HttpClient httpClient)
|
||||
{
|
||||
_settings = emailOptions.Value.Mailgun;
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
public async Task SendEmailAsync(string email, string subject, string htmlMessage)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_settings.ApiKey) || string.IsNullOrEmpty(_settings.Domain))
|
||||
{
|
||||
throw new Exception("Mailgun configuration is missing ApiKey or Domain.");
|
||||
}
|
||||
|
||||
var authToken = Encoding.ASCII.GetBytes($"api:{_settings.ApiKey}");
|
||||
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(authToken));
|
||||
|
||||
var formContent = new FormUrlEncodedContent(new[]
|
||||
{
|
||||
new KeyValuePair<string, string>("from", _settings.FromEmail),
|
||||
new KeyValuePair<string, string>("to", email),
|
||||
new KeyValuePair<string, string>("subject", subject),
|
||||
new KeyValuePair<string, string>("html", htmlMessage)
|
||||
});
|
||||
|
||||
var response = await _httpClient.PostAsync($"https://api.mailgun.net/v3/{_settings.Domain}/messages", formContent);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var errorBody = await response.Content.ReadAsStringAsync();
|
||||
throw new Exception($"Failed to send email via Mailgun. Status: {response.StatusCode}, Error: {errorBody}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Indotalent.Infrastructure.Email.Mailgun;
|
||||
|
||||
public class MailgunSettingsModel
|
||||
{
|
||||
public bool IsUsed { get; set; }
|
||||
public string ApiKey { get; set; } = string.Empty;
|
||||
public string Domain { get; set; } = string.Empty;
|
||||
public string FromEmail { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Indotalent.Infrastructure.Email.Mailjet;
|
||||
|
||||
public class MailjetEmailSender : IEmailSender
|
||||
{
|
||||
private readonly MailjetSettingsModel _settings;
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public MailjetEmailSender(IOptions<EmailSettingsModel> emailOptions, HttpClient httpClient)
|
||||
{
|
||||
_settings = emailOptions.Value.Mailjet;
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
public async Task SendEmailAsync(string email, string subject, string htmlMessage)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_settings.ApiKey) || string.IsNullOrEmpty(_settings.ApiSecret))
|
||||
{
|
||||
throw new Exception("Mailjet configuration is missing ApiKey or ApiSecret.");
|
||||
}
|
||||
|
||||
var authToken = Encoding.UTF8.GetBytes($"{_settings.ApiKey}:{_settings.ApiSecret}");
|
||||
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(authToken));
|
||||
|
||||
var payload = new
|
||||
{
|
||||
Messages = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
From = new { Email = _settings.FromEmail, Name = "Mailjet System" },
|
||||
To = new[] { new { Email = email } },
|
||||
Subject = subject,
|
||||
HTMLPart = htmlMessage
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
|
||||
var response = await _httpClient.PostAsync("https://api.mailjet.com/v3.1/send", content);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var errorBody = await response.Content.ReadAsStringAsync();
|
||||
throw new Exception($"Failed to send email via Mailjet. Status: {response.StatusCode}, Error: {errorBody}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Indotalent.Infrastructure.Email.Mailjet;
|
||||
|
||||
public class MailjetSettingsModel
|
||||
{
|
||||
public bool IsUsed { get; set; }
|
||||
public string ApiKey { get; set; } = string.Empty;
|
||||
public string ApiSecret { get; set; } = string.Empty;
|
||||
public string FromEmail { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Indotalent.Infrastructure.Email.SendGrid;
|
||||
|
||||
public class SendGridEmailSender : IEmailSender
|
||||
{
|
||||
private readonly SendGridSettingsModel _settings;
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public SendGridEmailSender(IOptions<EmailSettingsModel> emailOptions, HttpClient httpClient)
|
||||
{
|
||||
_settings = emailOptions.Value.SendGrid;
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
public async Task SendEmailAsync(string email, string subject, string htmlMessage)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_settings.ApiKey))
|
||||
{
|
||||
throw new Exception("SendGrid configuration is missing ApiKey.");
|
||||
}
|
||||
|
||||
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _settings.ApiKey);
|
||||
|
||||
var payload = new
|
||||
{
|
||||
personalizations = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
to = new[] { new { email = email } }
|
||||
}
|
||||
},
|
||||
from = new { email = _settings.FromEmail, name = "SendGrid System" },
|
||||
subject = subject,
|
||||
content = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
type = "text/html",
|
||||
value = htmlMessage
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
|
||||
var response = await _httpClient.PostAsync("https://api.sendgrid.com/v3/mail/send", content);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var errorBody = await response.Content.ReadAsStringAsync();
|
||||
throw new Exception($"Failed to send email via SendGrid. Status: {response.StatusCode}, Error: {errorBody}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Indotalent.Infrastructure.Email.SendGrid;
|
||||
|
||||
public class SendGridSettingsModel
|
||||
{
|
||||
public bool IsUsed { get; set; }
|
||||
public string ApiKey { get; set; } = string.Empty;
|
||||
public string FromEmail { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using MailKit.Net.Smtp;
|
||||
using MailKit.Security;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MimeKit;
|
||||
|
||||
namespace Indotalent.Infrastructure.Email.Smtp;
|
||||
|
||||
public class SmtpEmailSender : IEmailSender
|
||||
{
|
||||
private readonly SmtpSettingsModel _settings;
|
||||
|
||||
public SmtpEmailSender(IOptions<EmailSettingsModel> emailOptions)
|
||||
{
|
||||
_settings = emailOptions.Value.Smtp;
|
||||
}
|
||||
|
||||
public async Task SendEmailAsync(string email, string subject, string htmlMessage)
|
||||
{
|
||||
var message = new MimeMessage();
|
||||
|
||||
message.From.Add(new MailboxAddress(_settings.FromName, _settings.FromAddress));
|
||||
|
||||
message.To.Add(MailboxAddress.Parse(email));
|
||||
|
||||
message.Subject = subject;
|
||||
|
||||
var bodyBuilder = new BodyBuilder
|
||||
{
|
||||
HtmlBody = htmlMessage
|
||||
};
|
||||
message.Body = bodyBuilder.ToMessageBody();
|
||||
|
||||
using var client = new SmtpClient();
|
||||
try
|
||||
{
|
||||
await client.ConnectAsync(_settings.Host, _settings.Port, SecureSocketOptions.Auto);
|
||||
|
||||
if (!string.IsNullOrEmpty(_settings.UserName))
|
||||
{
|
||||
await client.AuthenticateAsync(_settings.UserName, _settings.Password);
|
||||
}
|
||||
|
||||
await client.SendAsync(message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"Failed to send email via SMTP to {email} via {_settings.Host}. Error: {ex.Message}", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await client.DisconnectAsync(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Indotalent.Infrastructure.Email.Smtp;
|
||||
|
||||
public class SmtpSettingsModel
|
||||
{
|
||||
public bool IsUsed { get; set; }
|
||||
public string Host { get; set; } = string.Empty;
|
||||
public int Port { get; set; }
|
||||
public string UserName { get; set; } = string.Empty;
|
||||
public string Password { get; set; } = string.Empty;
|
||||
public string FromAddress { get; set; } = string.Empty;
|
||||
public string FromName { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Indotalent.Infrastructure.File.Avatar;
|
||||
|
||||
public class AvatarStorageModel
|
||||
{
|
||||
public bool IsUsed { get; set; }
|
||||
public string StoragePath { get; set; } = "wwwroot/avatars";
|
||||
public string BaseUrl { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Indotalent.Infrastructure.File.Aws;
|
||||
|
||||
public class AwsS3SettingsModel
|
||||
{
|
||||
public bool IsUsed { get; set; }
|
||||
public string AccessKey { get; set; } = string.Empty;
|
||||
public string SecretKey { get; set; } = string.Empty;
|
||||
public string BucketName { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Indotalent.Infrastructure.File.Azure;
|
||||
|
||||
public class AzureBlobSettingsModel
|
||||
{
|
||||
public bool IsUsed { get; set; }
|
||||
public string ConnectionString { get; set; } = string.Empty;
|
||||
public string ContainerName { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
|
||||
namespace Indotalent.Infrastructure.File;
|
||||
|
||||
public static class DI
|
||||
{
|
||||
public static IServiceCollection AddFileService(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<FileStorageService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Indotalent.Infrastructure.File.Dropbox;
|
||||
|
||||
public class DropboxSettingsModel
|
||||
{
|
||||
public bool IsUsed { get; set; }
|
||||
public string AccessToken { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Indotalent.Infrastructure.File;
|
||||
|
||||
public class FileStorageService
|
||||
{
|
||||
private readonly FileStorageSettingsModel _settings;
|
||||
private readonly IWebHostEnvironment _environment;
|
||||
|
||||
private readonly string[] _allowedAvatarExtensions = { ".jpg", ".jpeg", ".png" };
|
||||
private readonly string[] _allowedDocExtensions = { ".pdf", ".xls", ".xlsx", ".doc", ".docx", ".txt" };
|
||||
|
||||
public FileStorageService(IOptions<FileStorageSettingsModel> options, IWebHostEnvironment environment)
|
||||
{
|
||||
_settings = options.Value;
|
||||
_environment = environment;
|
||||
}
|
||||
|
||||
public FileStorageSettingsModel GetConfiguration() => _settings;
|
||||
|
||||
private string GetPhysicalPath(string subFolder)
|
||||
{
|
||||
return Path.Combine(_environment.ContentRootPath, "wwwroot", subFolder);
|
||||
}
|
||||
|
||||
public async Task<string> SaveAvatarAsync(string userId, byte[] fileData, string extension)
|
||||
{
|
||||
var avatarSettings = _settings.Avatar;
|
||||
if (!avatarSettings.IsUsed) throw new Exception("Avatar storage is disabled.");
|
||||
|
||||
var ext = extension.ToLower();
|
||||
if (!_allowedAvatarExtensions.Contains(ext))
|
||||
throw new Exception("Invalid image type. Only JPG, JPEG, and PNG are allowed.");
|
||||
|
||||
var folderPath = GetPhysicalPath(avatarSettings.StoragePath);
|
||||
|
||||
if (!Directory.Exists(folderPath))
|
||||
{
|
||||
Directory.CreateDirectory(folderPath);
|
||||
}
|
||||
|
||||
var fileName = $"{Guid.NewGuid()}{ext}";
|
||||
var filePath = Path.Combine(folderPath, fileName);
|
||||
|
||||
await System.IO.File.WriteAllBytesAsync(filePath, fileData);
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public void DeleteOldAvatar(string? fileName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(fileName)) return;
|
||||
|
||||
try
|
||||
{
|
||||
var folderPath = GetPhysicalPath(_settings.Avatar.StoragePath);
|
||||
var filePath = Path.Combine(folderPath, fileName);
|
||||
|
||||
if (System.IO.File.Exists(filePath))
|
||||
{
|
||||
System.IO.File.Delete(filePath);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
public async Task<string> SaveFileAsync(byte[] fileData, string extension)
|
||||
{
|
||||
var localSettings = _settings.Local;
|
||||
if (!localSettings.IsUsed) throw new Exception("Local file storage is disabled.");
|
||||
|
||||
var ext = extension.ToLower();
|
||||
if (!_allowedDocExtensions.Contains(ext))
|
||||
throw new Exception($"File type {ext} is not allowed.");
|
||||
|
||||
var folderPath = GetPhysicalPath(localSettings.StoragePath);
|
||||
|
||||
if (!Directory.Exists(folderPath))
|
||||
{
|
||||
Directory.CreateDirectory(folderPath);
|
||||
}
|
||||
|
||||
var fileName = $"{Guid.NewGuid()}{ext}";
|
||||
var filePath = Path.Combine(folderPath, fileName);
|
||||
|
||||
await System.IO.File.WriteAllBytesAsync(filePath, fileData);
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public void DeleteFile(string? fileName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(fileName)) return;
|
||||
|
||||
try
|
||||
{
|
||||
var folderPath = GetPhysicalPath(_settings.Local.StoragePath);
|
||||
var filePath = Path.Combine(folderPath, fileName);
|
||||
|
||||
if (System.IO.File.Exists(filePath))
|
||||
{
|
||||
System.IO.File.Delete(filePath);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Indotalent.Infrastructure.File.Avatar;
|
||||
using Indotalent.Infrastructure.File.Aws;
|
||||
using Indotalent.Infrastructure.File.Azure;
|
||||
using Indotalent.Infrastructure.File.Dropbox;
|
||||
using Indotalent.Infrastructure.File.Google;
|
||||
using Indotalent.Infrastructure.File.Local;
|
||||
|
||||
namespace Indotalent.Infrastructure.File;
|
||||
|
||||
public class FileStorageSettingsModel
|
||||
{
|
||||
public AvatarStorageModel Avatar { get; set; } = new();
|
||||
public LocalStorageModel Local { get; set; } = new();
|
||||
public AwsS3SettingsModel AwsS3 { get; set; } = new();
|
||||
public AzureBlobSettingsModel AzureBlob { get; set; } = new();
|
||||
public GoogleCloudSettingsModel GoogleCloud { get; set; } = new();
|
||||
public DropboxSettingsModel Dropbox { get; set; } = new();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Indotalent.Infrastructure.File.Google;
|
||||
|
||||
public class GoogleCloudSettingsModel
|
||||
{
|
||||
public bool IsUsed { get; set; }
|
||||
public string ProjectId { get; set; } = string.Empty;
|
||||
public string BucketName { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Indotalent.Infrastructure.File.Local;
|
||||
|
||||
public class LocalStorageModel
|
||||
{
|
||||
public bool IsUsed { get; set; }
|
||||
public string StoragePath { get; set; } = "wwwroot/uploads";
|
||||
public string BaseUrl { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Indotalent.Infrastructure.Logging;
|
||||
|
||||
public static class DI
|
||||
{
|
||||
public static IServiceCollection AddLoggingService(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<LoggingService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Indotalent.Infrastructure.Logging.Serilog;
|
||||
|
||||
namespace Indotalent.Infrastructure.Logging;
|
||||
|
||||
public class LoggerSettingsModel
|
||||
{
|
||||
public FileLoggerModel File { get; set; } = new();
|
||||
public DatabaseLoggerModel Database { get; set; } = new();
|
||||
public SeqLoggerModel Seq { get; set; } = new();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Indotalent.Infrastructure.Logging;
|
||||
|
||||
public class LoggingService(IOptions<LoggerSettingsModel> options)
|
||||
{
|
||||
private readonly LoggerSettingsModel _settings = options.Value;
|
||||
|
||||
public LoggerSettingsModel GetConfiguration() => _settings;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Indotalent.Infrastructure.Logging.Serilog;
|
||||
|
||||
public class DatabaseLoggerModel
|
||||
{
|
||||
public bool IsUsed { get; set; }
|
||||
public string TableName { get; set; } = "SerilogLogs";
|
||||
public bool AutoCreateSqlTable { get; set; } = true;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Indotalent.Infrastructure.Logging.Serilog;
|
||||
|
||||
public class FileLoggerModel
|
||||
{
|
||||
public bool IsUsed { get; set; }
|
||||
public string Path { get; set; } = "Logs/log-.txt";
|
||||
public string RollingInterval { get; set; } = "Day"; // Day, Hour, Month
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Indotalent.Infrastructure.Logging.Serilog;
|
||||
|
||||
public class SeqLoggerModel
|
||||
{
|
||||
public bool IsUsed { get; set; }
|
||||
public string ServerUrl { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
using Serilog;
|
||||
using Serilog.Enrichers.Sensitive;
|
||||
using Serilog.Events;
|
||||
using Serilog.Sinks.MSSqlServer;
|
||||
|
||||
namespace Indotalent.Infrastructure.Logging.Serilog;
|
||||
|
||||
public static class SerilogBuilder
|
||||
{
|
||||
public static void AddSerilogBuilder(this WebApplicationBuilder builder)
|
||||
{
|
||||
var settings = builder.Configuration.GetSection("LoggerSettings").Get<LoggerSettingsModel>();
|
||||
|
||||
var loggerConfiguration = new LoggerConfiguration()
|
||||
.ReadFrom.Configuration(builder.Configuration)
|
||||
.Enrich.FromLogContext()
|
||||
.Enrich.WithMachineName()
|
||||
.Enrich.WithThreadId()
|
||||
.Enrich.WithSensitiveDataMasking(options =>
|
||||
{
|
||||
options.MaskValue = "***MASKED***";
|
||||
|
||||
options.MaskProperties.Add(MaskProperty.WithDefaults("Password"));
|
||||
options.MaskProperties.Add(MaskProperty.WithDefaults("Secret"));
|
||||
options.MaskProperties.Add(MaskProperty.WithDefaults("Token"));
|
||||
options.MaskProperties.Add(MaskProperty.WithDefaults("ApiKey"));
|
||||
options.MaskProperties.Add(MaskProperty.WithDefaults("ConfirmPassword"));
|
||||
|
||||
})
|
||||
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}");
|
||||
|
||||
if (settings?.File?.IsUsed == true)
|
||||
{
|
||||
var interval = settings.File.RollingInterval.ToLower() switch
|
||||
{
|
||||
"hour" => RollingInterval.Hour,
|
||||
"month" => RollingInterval.Month,
|
||||
_ => RollingInterval.Day
|
||||
};
|
||||
|
||||
loggerConfiguration.WriteTo.File(
|
||||
path: settings.File.Path,
|
||||
rollingInterval: interval,
|
||||
restrictedToMinimumLevel: LogEventLevel.Error,
|
||||
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}");
|
||||
}
|
||||
|
||||
if (settings?.Database?.IsUsed == true)
|
||||
{
|
||||
var columnOptions = new ColumnOptions();
|
||||
columnOptions.Store.Add(StandardColumn.LogEvent);
|
||||
columnOptions.LogEvent.ColumnName = "LogEvent";
|
||||
var connectionString = builder.Configuration.GetSection("DatabaseSettings:MsSQL:ConnectionString").Value;
|
||||
|
||||
if (!string.IsNullOrEmpty(connectionString))
|
||||
{
|
||||
loggerConfiguration.WriteTo.MSSqlServer(
|
||||
connectionString: connectionString,
|
||||
sinkOptions: new MSSqlServerSinkOptions
|
||||
{
|
||||
TableName = settings.Database.TableName,
|
||||
AutoCreateSqlTable = settings.Database.AutoCreateSqlTable
|
||||
},
|
||||
columnOptions: columnOptions,
|
||||
restrictedToMinimumLevel: LogEventLevel.Error);
|
||||
}
|
||||
}
|
||||
|
||||
if (settings?.Seq?.IsUsed == true && !string.IsNullOrEmpty(settings.Seq.ServerUrl))
|
||||
{
|
||||
// Send logs to the Seq server (Self-hosted dashboard by Datalust [https://datalust.co/])
|
||||
loggerConfiguration.WriteTo.Seq(
|
||||
serverUrl: settings.Seq.ServerUrl,
|
||||
restrictedToMinimumLevel: LogEventLevel.Information
|
||||
);
|
||||
}
|
||||
|
||||
Log.Logger = loggerConfiguration.CreateLogger();
|
||||
builder.Host.UseSerilog();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Shared.Consts;
|
||||
using Microsoft.AspNetCore.OData;
|
||||
using Microsoft.OData.Edm;
|
||||
using Microsoft.OData.ModelBuilder;
|
||||
|
||||
namespace Indotalent.Infrastructure.OData;
|
||||
|
||||
public static class DI
|
||||
{
|
||||
public static IServiceCollection AddODataService(this IServiceCollection services)
|
||||
{
|
||||
services.AddControllers().AddOData(options =>
|
||||
{
|
||||
options.AddRouteComponents("odata", GetEdmModel())
|
||||
.Select()
|
||||
.Filter()
|
||||
.OrderBy()
|
||||
.Expand()
|
||||
.Count()
|
||||
.SetMaxTop(GlobalConsts.ODataMaxTop);
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
private static IEdmModel GetEdmModel()
|
||||
{
|
||||
var builder = new ODataConventionModelBuilder();
|
||||
|
||||
builder.EntitySet<SerilogLogs>("SerilogLogs");
|
||||
|
||||
return builder.GetEdmModel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Indotalent.Infrastructure.OData;
|
||||
|
||||
public class ODataResponse<T>
|
||||
{
|
||||
[JsonPropertyName("value")]
|
||||
public List<T> Value { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("@odata.count")]
|
||||
public int Count { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user