issue #2551
All checks were successful
Build & Deploy Leave Service / build (push) Successful in 2m58s
All checks were successful
Build & Deploy Leave Service / build (push) Successful in 2m58s
This commit is contained in:
parent
71966eb4e9
commit
2cdae3578e
7 changed files with 1996 additions and 40 deletions
|
|
@ -9,6 +9,7 @@ using BMA.EHR.Domain.Shared;
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
|
||||
{
|
||||
|
|
@ -23,6 +24,12 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
|
|||
private readonly IConfiguration _configuration;
|
||||
private readonly EmailSenderService _emailSenderService;
|
||||
|
||||
/// <summary>
|
||||
/// Keyed locks to serialize get-or-create for LeaveBeginning rows by (ProfileId, LeaveYear, LeaveTypeId).
|
||||
/// Prevents duplicate inserts when concurrent requests (e.g. UI calling /user/check twice) hit the same key.
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<string, SemaphoreSlim> _getOrAddLocks = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region " Constructor and Destuctor "
|
||||
|
|
@ -121,6 +128,27 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
|
|||
await _dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task ProcessEarlyLeaveRequest(int year)
|
||||
{
|
||||
// Get Early Leave Request
|
||||
var leaveReq = await _dbContext.Set<LeaveRequest>()
|
||||
.Include(x => x.Type)
|
||||
.Where(x => x.LeaveStatus == "APPROVE")
|
||||
.Where(x => x.LeaveStartDate.Year == year || x.LeaveEndDate.Year == year)
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var leave in leaveReq)
|
||||
{
|
||||
await GetByYearAndTypeIdForUserAsync(year, leave.Type.Id, leave.KeycloakUserId);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessEarlyLeaveRequestSchedule()
|
||||
{
|
||||
int year = DateTime.Now.Year;
|
||||
await ProcessEarlyLeaveRequest(year);
|
||||
}
|
||||
|
||||
public async Task<LeaveBeginning?> GetByYearAndTypeIdForUserAsync(int year, Guid typeId, Guid userId)
|
||||
{
|
||||
// var pf = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken);
|
||||
|
|
@ -134,17 +162,13 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
|
|||
|
||||
var leaveType = await _dbContext.Set<LeaveType>().FirstOrDefaultAsync(x => x.Id == typeId);
|
||||
|
||||
var data = await _dbContext.Set<LeaveBeginning>()
|
||||
.Include(x => x.LeaveType)
|
||||
.FirstOrDefaultAsync(x => x.LeaveYear == year && x.LeaveTypeId == typeId && x.ProfileId == pf.Id);
|
||||
|
||||
if (data == null)
|
||||
LeaveBeginning Factory()
|
||||
{
|
||||
var limit = 0.0;
|
||||
|
||||
var prev = await _dbContext.Set<LeaveBeginning>()
|
||||
var prev = _dbContext.Set<LeaveBeginning>()
|
||||
.Include(x => x.LeaveType)
|
||||
.FirstOrDefaultAsync(x => x.LeaveYear == year - 1 && x.LeaveTypeId == typeId && x.ProfileId == pf.Id);
|
||||
.FirstOrDefault(x => x.LeaveYear == year - 1 && x.LeaveTypeId == typeId && x.ProfileId == pf.Id);
|
||||
|
||||
var prevRemain = 0.0;
|
||||
if (prev != null)
|
||||
|
|
@ -170,7 +194,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
|
|||
limit = 0.0;
|
||||
}
|
||||
|
||||
data = new LeaveBeginning
|
||||
return new LeaveBeginning
|
||||
{
|
||||
LeaveYear = year,
|
||||
LeaveTypeId = typeId,
|
||||
|
|
@ -186,12 +210,9 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
|
|||
Child3DnaId = pf.Child3DnaId,
|
||||
Child4DnaId = pf.Child4DnaId
|
||||
};
|
||||
|
||||
_dbContext.Set<LeaveBeginning>().Add(data);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return data;
|
||||
return await GetOrAddForUserAsync(year, typeId, pf.Id, Factory);
|
||||
}
|
||||
|
||||
public async Task<LeaveBeginning?> GetByYearAndTypeIdForUser(int year, Guid typeId, GetProfileByKeycloakIdDto? pf)
|
||||
|
|
@ -200,17 +221,13 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
|
|||
|
||||
var leaveType = await _dbContext.Set<LeaveType>().FirstOrDefaultAsync(x => x.Id == typeId);
|
||||
|
||||
var data = await _dbContext.Set<LeaveBeginning>()
|
||||
.Include(x => x.LeaveType)
|
||||
.FirstOrDefaultAsync(x => x.LeaveYear == year && x.LeaveTypeId == typeId && x.ProfileId == pf.Id);
|
||||
|
||||
if (data == null)
|
||||
LeaveBeginning Factory()
|
||||
{
|
||||
var limit = 0.0;
|
||||
|
||||
var prev = await _dbContext.Set<LeaveBeginning>()
|
||||
var prev = _dbContext.Set<LeaveBeginning>()
|
||||
.Include(x => x.LeaveType)
|
||||
.FirstOrDefaultAsync(x => x.LeaveYear == year - 1 && x.LeaveTypeId == typeId && x.ProfileId == pf.Id);
|
||||
.FirstOrDefault(x => x.LeaveYear == year - 1 && x.LeaveTypeId == typeId && x.ProfileId == pf.Id);
|
||||
|
||||
var prevRemain = 0.0;
|
||||
if (prev != null)
|
||||
|
|
@ -236,7 +253,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
|
|||
limit = 0.0;
|
||||
}
|
||||
|
||||
data = new LeaveBeginning
|
||||
return new LeaveBeginning
|
||||
{
|
||||
LeaveYear = year,
|
||||
LeaveTypeId = typeId,
|
||||
|
|
@ -252,12 +269,9 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
|
|||
Child3DnaId = pf.Child3DnaId,
|
||||
Child4DnaId = pf.Child4DnaId
|
||||
};
|
||||
|
||||
_dbContext.Set<LeaveBeginning>().Add(data);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return data;
|
||||
return await GetOrAddForUserAsync(year, typeId, pf.Id, Factory);
|
||||
}
|
||||
|
||||
public async Task<LeaveBeginning?> GetByYearAndTypeIdForUser2Async(int year, Guid typeId, Guid userId)
|
||||
|
|
@ -273,17 +287,13 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
|
|||
|
||||
var leaveType = await _dbContext.Set<LeaveType>().FirstOrDefaultAsync(x => x.Id == typeId);
|
||||
|
||||
var data = await _dbContext.Set<LeaveBeginning>()
|
||||
.Include(x => x.LeaveType)
|
||||
.FirstOrDefaultAsync(x => x.LeaveYear == year && x.LeaveTypeId == typeId && x.ProfileId == pf.Id);
|
||||
|
||||
if (data == null)
|
||||
LeaveBeginning Factory()
|
||||
{
|
||||
var limit = 0.0;
|
||||
|
||||
var prev = await _dbContext.Set<LeaveBeginning>()
|
||||
var prev = _dbContext.Set<LeaveBeginning>()
|
||||
.Include(x => x.LeaveType)
|
||||
.FirstOrDefaultAsync(x => x.LeaveYear == year - 1 && x.LeaveTypeId == typeId && x.ProfileId == pf.Id);
|
||||
.FirstOrDefault(x => x.LeaveYear == year - 1 && x.LeaveTypeId == typeId && x.ProfileId == pf.Id);
|
||||
|
||||
var prevRemain = 0.0;
|
||||
if (prev != null)
|
||||
|
|
@ -309,7 +319,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
|
|||
limit = 0.0;
|
||||
}
|
||||
|
||||
data = new LeaveBeginning
|
||||
return new LeaveBeginning
|
||||
{
|
||||
LeaveYear = year,
|
||||
LeaveTypeId = typeId,
|
||||
|
|
@ -325,17 +335,59 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
|
|||
Child3DnaId = pf.Child3DnaId,
|
||||
Child4DnaId = pf.Child4DnaId
|
||||
};
|
||||
|
||||
_dbContext.Set<LeaveBeginning>().Add(data);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return data;
|
||||
return await GetOrAddForUserAsync(year, typeId, pf.Id, Factory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get-or-create a LeaveBeginning row for (ProfileId, LeaveYear, LeaveTypeId) with concurrency protection.
|
||||
/// Uses a keyed SemaphoreSlim to serialize within-process requests, and re-queries after acquiring the lock.
|
||||
/// If a cross-process insert wins (unique index violation), the duplicate key exception is caught and the row
|
||||
/// created by the winner is returned.
|
||||
/// </summary>
|
||||
private async Task<LeaveBeginning?> GetOrAddForUserAsync(int year, Guid typeId, Guid profileId, Func<LeaveBeginning> factory)
|
||||
{
|
||||
var key = $"{profileId}_{year}_{typeId}";
|
||||
var semaphore = _getOrAddLocks.GetOrAdd(key, _ => new SemaphoreSlim(1, 1));
|
||||
await semaphore.WaitAsync();
|
||||
try
|
||||
{
|
||||
// Re-query inside the lock — another thread may have created it while we waited.
|
||||
var existing = await _dbContext.Set<LeaveBeginning>()
|
||||
.Include(x => x.LeaveType)
|
||||
.FirstOrDefaultAsync(x => x.LeaveYear == year && x.LeaveTypeId == typeId && x.ProfileId == profileId);
|
||||
if (existing != null)
|
||||
{
|
||||
return existing;
|
||||
}
|
||||
|
||||
var entity = factory();
|
||||
_dbContext.Set<LeaveBeginning>().Add(entity);
|
||||
try
|
||||
{
|
||||
await _dbContext.SaveChangesAsync();
|
||||
return entity;
|
||||
}
|
||||
catch (DbUpdateException)
|
||||
{
|
||||
// Cross-process/cross-server race hit the unique index (IX_LeaveBeginnings_ProfileId_LeaveYear_LeaveTypeId).
|
||||
// Detach the failed insert and return the row created by the winner.
|
||||
_dbContext.Detach(entity);
|
||||
var winner = await _dbContext.Set<LeaveBeginning>()
|
||||
.Include(x => x.LeaveType)
|
||||
.FirstOrDefaultAsync(x => x.LeaveYear == year && x.LeaveTypeId == typeId && x.ProfileId == profileId);
|
||||
return winner;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<LeaveBeginning>> GetAllByYearAndTypeAsync(int year, Guid typeId, List<ProfileData> userIdList)
|
||||
{
|
||||
|
||||
|
||||
var updateList = new List<LeaveBeginning>();
|
||||
var result = new List<LeaveBeginning>();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue