initial commit

This commit is contained in:
2026-07-21 14:08:10 +07:00
commit f7cf118326
533 changed files with 41762 additions and 0 deletions
+88
View File
@@ -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
};
}
@@ -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 Member = "Member";
public const string Guest = "Guest";
public static IReadOnlyList<string> AllRoles => new[] { Admin, Member, Guest };
}
@@ -0,0 +1,94 @@
using Indotalent.Data.Entities;
using Indotalent.Infrastructure.Database;
using Microsoft.EntityFrameworkCore;
using System.Collections.Concurrent;
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 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 = $"{entityName}_{year}_{month}";
var semaphore = _locks.GetOrAdd(lockKey, _ => new SemaphoreSlim(1, 1));
await semaphore.WaitAsync(cancellationToken);
try
{
var sequence = await _context.AutoNumberSequence
.FirstOrDefaultAsync(ns =>
ns.EntityName == entityName &&
ns.Year == year &&
ns.Month == month,
cancellationToken);
if (sequence == null)
{
sequence = new AutoNumberSequence
{
EntityName = entityName,
Year = year,
Month = month,
CurrentSequence = 0,
PrefixTemplate = prefixTemplate,
SuffixTemplate = suffixTemplate,
PaddingLength = paddingLength
};
_context.AutoNumberSequence.Add(sequence);
}
sequence.CurrentSequence = (sequence.CurrentSequence ?? 0) + 1;
var formattedNumber = BuildFormattedNumber(sequence, now);
await _context.SaveChangesAsync(cancellationToken);
return formattedNumber;
}
finally
{
semaphore.Release();
if (semaphore.CurrentCount == 1) _locks.TryRemove(lockKey, out _);
}
}
private string BuildFormattedNumber(AutoNumberSequence sequence, DateTime now)
{
string ProcessTemplate(string? template)
{
if (string.IsNullOrEmpty(template)) return "";
return template
.Replace("{EntityName}", sequence.EntityName)
.Replace("{Year}", now.Year.ToString())
.Replace("{Year2}", now.ToString("yy"))
.Replace("{Month}", now.Month.ToString("D2"));
}
var prefix = ProcessTemplate(sequence.PrefixTemplate);
var suffix = ProcessTemplate(sequence.SuffixTemplate);
var padding = sequence.PaddingLength ?? 4;
var paddedSequence = (sequence.CurrentSequence ?? 1).ToString($"D{padding}");
return $"{prefix}{paddedSequence}{suffix}";
}
}
+13
View File
@@ -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();
}
+11
View File
@@ -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_";
}
+43
View File
@@ -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;
}
}
+193
View File
@@ -0,0 +1,193 @@
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 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 override int SaveChanges()
{
ApplySoftDelete();
ApplyAudit(_currentUserService.UserId ?? string.Empty);
return base.SaveChanges();
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
ChangeTracker.DetectChanges();
ApplySoftDelete();
ApplyAudit(_currentUserService.UserId ?? string.Empty);
await ApplyAutoNumber(cancellationToken);
return await base.SaveChangesAsync(cancellationToken);
}
private void ApplySoftDelete()
{
var entries = ChangeTracker.Entries<IHasIsDeleted>()
.Where(e => e.State == EntityState.Deleted);
foreach (var entry in entries)
{
entry.State = EntityState.Modified;
entry.Entity.IsDeleted = true;
}
}
private void ApplyAudit(string userId)
{
var entries = ChangeTracker.Entries<IHasAudit>()
.Where(e => e.State == EntityState.Added || e.State == EntityState.Modified)
.ToList();
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 ApplyAutoNumber(CancellationToken ct)
{
var entries = ChangeTracker
.Entries<IHasAutoNumber>()
.Where(e => e.State == EntityState.Added && string.IsNullOrEmpty(e.Entity.AutoNumber))
.ToList();
if (!entries.Any()) return;
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(
entityName: entityName,
prefixTemplate: $"{shortName}/{{Year}}/",
paddingLength: 4,
cancellationToken: ct
);
}
}
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(IHasIsDeleted).IsAssignableFrom(type))
{
modelBuilder.Entity(type).HasQueryFilter(GetNotDeletedOnlyFilter(type));
}
}
modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);
}
private static System.Linq.Expressions.LambdaExpression GetNotDeletedOnlyFilter(Type type)
{
var parameter = System.Linq.Expressions.Expression.Parameter(type, "e");
var property = System.Linq.Expressions.Expression.Property(parameter, nameof(IHasIsDeleted.IsDeleted));
var falseConstant = System.Linq.Expressions.Expression.Constant(false);
var comparison = System.Linq.Expressions.Expression.Equal(property, falseConstant);
return System.Linq.Expressions.Expression.Lambda(comparison, parameter);
}
}
+50
View File
@@ -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; }
}
+650
View File
@@ -0,0 +1,650 @@
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
{
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>();
await SeedIdentityAsync(roleManager, userManager, configuration);
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 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);
}
}
}
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(a => a.AutoNumber).IsUnique();
builder.HasIndex(a => a.EmployeeId);
builder.HasOne(a => a.Employee)
.WithMany()
.HasForeignKey(a => a.EmployeeId)
.OnDelete(DeleteBehavior.NoAction);
}
}
@@ -0,0 +1,25 @@
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.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 => e.AutoNumber).IsUnique();
builder.HasIndex(e => 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 => 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 => e.AutoNumber).IsUnique();
builder.HasIndex(e => 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 => e.AutoNumber).IsUnique();
builder.HasIndex(e => 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 => e.AutoNumber).IsUnique();
builder.HasIndex(e => 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 => e.AutoNumber).IsUnique();
builder.HasIndex(e => 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 => e.AutoNumber).IsUnique();
builder.HasIndex(e => 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 => 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 => 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 => 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 => e.AutoNumber).IsUnique();
builder.HasIndex(e => 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 => e.AutoNumber).IsUnique();
builder.HasIndex(e => 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 => e.AutoNumber).IsUnique();
builder.HasIndex(e => e.Year);
builder.HasIndex(e => new { 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 => e.AutoNumber).IsUnique();
builder.HasIndex(e => 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 => 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 => 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(p => p.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 => e.AutoNumber).IsUnique();
builder.HasIndex(e => 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(t => t.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;
}
}
+46
View File
@@ -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;
}
}
+10
View File
@@ -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();
}
+6
View File
@@ -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;
}
+13
View File
@@ -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;
}
+107
View File
@@ -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;
}
+11
View File
@@ -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();
}
+10
View File
@@ -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();
}
}
+35
View File
@@ -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();
}
}
+12
View File
@@ -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; }
}