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.AspNetCore.Http;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
|
namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
|
||||||
{
|
{
|
||||||
|
|
@ -23,6 +24,12 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
|
||||||
private readonly IConfiguration _configuration;
|
private readonly IConfiguration _configuration;
|
||||||
private readonly EmailSenderService _emailSenderService;
|
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
|
#endregion
|
||||||
|
|
||||||
#region " Constructor and Destuctor "
|
#region " Constructor and Destuctor "
|
||||||
|
|
@ -121,6 +128,27 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
|
||||||
await _dbContext.SaveChangesAsync();
|
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)
|
public async Task<LeaveBeginning?> GetByYearAndTypeIdForUserAsync(int year, Guid typeId, Guid userId)
|
||||||
{
|
{
|
||||||
// var pf = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken);
|
// 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 leaveType = await _dbContext.Set<LeaveType>().FirstOrDefaultAsync(x => x.Id == typeId);
|
||||||
|
|
||||||
var data = await _dbContext.Set<LeaveBeginning>()
|
LeaveBeginning Factory()
|
||||||
.Include(x => x.LeaveType)
|
|
||||||
.FirstOrDefaultAsync(x => x.LeaveYear == year && x.LeaveTypeId == typeId && x.ProfileId == pf.Id);
|
|
||||||
|
|
||||||
if (data == null)
|
|
||||||
{
|
{
|
||||||
var limit = 0.0;
|
var limit = 0.0;
|
||||||
|
|
||||||
var prev = await _dbContext.Set<LeaveBeginning>()
|
var prev = _dbContext.Set<LeaveBeginning>()
|
||||||
.Include(x => x.LeaveType)
|
.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;
|
var prevRemain = 0.0;
|
||||||
if (prev != null)
|
if (prev != null)
|
||||||
|
|
@ -170,7 +194,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
|
||||||
limit = 0.0;
|
limit = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
data = new LeaveBeginning
|
return new LeaveBeginning
|
||||||
{
|
{
|
||||||
LeaveYear = year,
|
LeaveYear = year,
|
||||||
LeaveTypeId = typeId,
|
LeaveTypeId = typeId,
|
||||||
|
|
@ -186,12 +210,9 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
|
||||||
Child3DnaId = pf.Child3DnaId,
|
Child3DnaId = pf.Child3DnaId,
|
||||||
Child4DnaId = pf.Child4DnaId
|
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)
|
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 leaveType = await _dbContext.Set<LeaveType>().FirstOrDefaultAsync(x => x.Id == typeId);
|
||||||
|
|
||||||
var data = await _dbContext.Set<LeaveBeginning>()
|
LeaveBeginning Factory()
|
||||||
.Include(x => x.LeaveType)
|
|
||||||
.FirstOrDefaultAsync(x => x.LeaveYear == year && x.LeaveTypeId == typeId && x.ProfileId == pf.Id);
|
|
||||||
|
|
||||||
if (data == null)
|
|
||||||
{
|
{
|
||||||
var limit = 0.0;
|
var limit = 0.0;
|
||||||
|
|
||||||
var prev = await _dbContext.Set<LeaveBeginning>()
|
var prev = _dbContext.Set<LeaveBeginning>()
|
||||||
.Include(x => x.LeaveType)
|
.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;
|
var prevRemain = 0.0;
|
||||||
if (prev != null)
|
if (prev != null)
|
||||||
|
|
@ -236,7 +253,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
|
||||||
limit = 0.0;
|
limit = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
data = new LeaveBeginning
|
return new LeaveBeginning
|
||||||
{
|
{
|
||||||
LeaveYear = year,
|
LeaveYear = year,
|
||||||
LeaveTypeId = typeId,
|
LeaveTypeId = typeId,
|
||||||
|
|
@ -252,12 +269,9 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
|
||||||
Child3DnaId = pf.Child3DnaId,
|
Child3DnaId = pf.Child3DnaId,
|
||||||
Child4DnaId = pf.Child4DnaId
|
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)
|
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 leaveType = await _dbContext.Set<LeaveType>().FirstOrDefaultAsync(x => x.Id == typeId);
|
||||||
|
|
||||||
var data = await _dbContext.Set<LeaveBeginning>()
|
LeaveBeginning Factory()
|
||||||
.Include(x => x.LeaveType)
|
|
||||||
.FirstOrDefaultAsync(x => x.LeaveYear == year && x.LeaveTypeId == typeId && x.ProfileId == pf.Id);
|
|
||||||
|
|
||||||
if (data == null)
|
|
||||||
{
|
{
|
||||||
var limit = 0.0;
|
var limit = 0.0;
|
||||||
|
|
||||||
var prev = await _dbContext.Set<LeaveBeginning>()
|
var prev = _dbContext.Set<LeaveBeginning>()
|
||||||
.Include(x => x.LeaveType)
|
.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;
|
var prevRemain = 0.0;
|
||||||
if (prev != null)
|
if (prev != null)
|
||||||
|
|
@ -309,7 +319,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
|
||||||
limit = 0.0;
|
limit = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
data = new LeaveBeginning
|
return new LeaveBeginning
|
||||||
{
|
{
|
||||||
LeaveYear = year,
|
LeaveYear = year,
|
||||||
LeaveTypeId = typeId,
|
LeaveTypeId = typeId,
|
||||||
|
|
@ -325,18 +335,60 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests
|
||||||
Child3DnaId = pf.Child3DnaId,
|
Child3DnaId = pf.Child3DnaId,
|
||||||
Child4DnaId = pf.Child4DnaId
|
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)
|
public async Task<List<LeaveBeginning>> GetAllByYearAndTypeAsync(int year, Guid typeId, List<ProfileData> userIdList)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
var updateList = new List<LeaveBeginning>();
|
var updateList = new List<LeaveBeginning>();
|
||||||
var result = new List<LeaveBeginning>();
|
var result = new List<LeaveBeginning>();
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,28 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace BMA.EHR.Infrastructure.Migrations.LeaveDb
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddUniqueIndexLeaveBeginningProfileIdYearTypeId : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_LeaveBeginnings_ProfileId_LeaveYear_LeaveTypeId",
|
||||||
|
table: "LeaveBeginnings",
|
||||||
|
columns: new[] { "ProfileId", "LeaveYear", "LeaveTypeId" },
|
||||||
|
unique: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_LeaveBeginnings_ProfileId_LeaveYear_LeaveTypeId",
|
||||||
|
table: "LeaveBeginnings");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -226,6 +226,10 @@ namespace BMA.EHR.Infrastructure.Migrations.LeaveDb
|
||||||
|
|
||||||
b.HasIndex("LeaveTypeId");
|
b.HasIndex("LeaveTypeId");
|
||||||
|
|
||||||
|
b.HasIndex("ProfileId", "LeaveYear", "LeaveTypeId")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("IX_LeaveBeginnings_ProfileId_LeaveYear_LeaveTypeId");
|
||||||
|
|
||||||
b.ToTable("LeaveBeginnings");
|
b.ToTable("LeaveBeginnings");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,5 +61,18 @@ namespace BMA.EHR.Infrastructure.Persistence
|
||||||
{
|
{
|
||||||
base.Entry(entity).State = EntityState.Detached;
|
base.Entry(entity).State = EntityState.Detached;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
base.OnModelCreating(modelBuilder);
|
||||||
|
|
||||||
|
// Composite unique index on the natural key for LeaveBeginning.
|
||||||
|
// Prevents duplicate rows when concurrent requests (e.g. UI calling /user/check twice)
|
||||||
|
// race through the get-or-create flow in LeaveBeginningRepository.
|
||||||
|
modelBuilder.Entity<LeaveBeginning>()
|
||||||
|
.HasIndex(b => new { b.ProfileId, b.LeaveYear, b.LeaveTypeId })
|
||||||
|
.HasDatabaseName("IX_LeaveBeginnings_ProfileId_LeaveYear_LeaveTypeId")
|
||||||
|
.IsUnique();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1016,6 +1016,30 @@ namespace BMA.EHR.Leave.Service.Controllers
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ทดสอบประมวลผล beginning
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="year"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpGet("process-beginning/{year:int}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<ActionResult<ResponseObject>> ProcessBeginningByYearAsync([FromRoute] int year)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _leaveBeginningRepository.ProcessEarlyLeaveRequest(year);
|
||||||
|
return Success();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// LV2_003 - เช็คการยืนขอลา (USER)
|
/// LV2_003 - เช็คการยืนขอลา (USER)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ using System.Text;
|
||||||
using Hangfire;
|
using Hangfire;
|
||||||
using Hangfire.MySql;
|
using Hangfire.MySql;
|
||||||
using System.Transactions;
|
using System.Transactions;
|
||||||
|
using BMA.EHR.Application.Repositories.Leaves.LeaveRequests;
|
||||||
using BMA.EHR.Leave.Service.Filters;
|
using BMA.EHR.Leave.Service.Filters;
|
||||||
using Hangfire.Common;
|
using Hangfire.Common;
|
||||||
using BMA.EHR.Application.Repositories.Leaves.TimeAttendants;
|
using BMA.EHR.Application.Repositories.Leaves.TimeAttendants;
|
||||||
|
|
@ -194,9 +195,27 @@ app.UseHangfireDashboard("/hangfire", new DashboardOptions()
|
||||||
var manager = new RecurringJobManager();
|
var manager = new RecurringJobManager();
|
||||||
if (manager != null)
|
if (manager != null)
|
||||||
{
|
{
|
||||||
manager.AddOrUpdate("ปรับปรุงรอบการลงเวลาทำงาน", Job.FromExpression<UserDutyTimeRepository>(x => x.UpdateUserDutyTime()), "0 1 * * *", bangkokTimeZone);
|
manager.AddOrUpdate("ปรับปรุงรอบการลงเวลาทำงาน", Job.FromExpression<UserDutyTimeRepository>(x => x.UpdateUserDutyTime()), "0 1 * * *",
|
||||||
|
new RecurringJobOptions
|
||||||
|
{
|
||||||
|
TimeZone = bangkokTimeZone,
|
||||||
|
QueueName = "leave"
|
||||||
|
});
|
||||||
// ทำความสะอาดข้อมูล CheckIn Job Status ที่เก่ากว่า 30 วัน - รันทุกวันเวลา 02:00 น.
|
// ทำความสะอาดข้อมูล CheckIn Job Status ที่เก่ากว่า 30 วัน - รันทุกวันเวลา 02:00 น.
|
||||||
manager.AddOrUpdate("ทำความสะอาดข้อมูล CheckIn Job Status", Job.FromExpression<CheckInJobStatusRepository>(x => x.CleanupOldJobsAsync(30)), "0 2 * * *", bangkokTimeZone);
|
manager.AddOrUpdate("ทำความสะอาดข้อมูล CheckIn Job Status", Job.FromExpression<CheckInJobStatusRepository>(x => x.CleanupOldJobsAsync(30)), "0 2 * * *",
|
||||||
|
new RecurringJobOptions
|
||||||
|
{
|
||||||
|
TimeZone = bangkokTimeZone,
|
||||||
|
QueueName = "leave"
|
||||||
|
});
|
||||||
|
|
||||||
|
manager.AddOrUpdate("Proceess Beginning สำหรับการลาล่วงหน้า", Job.FromExpression<LeaveBeginningRepository>(x => x.ProcessEarlyLeaveRequestSchedule()), "0 1 1 10 *",
|
||||||
|
new RecurringJobOptions
|
||||||
|
{
|
||||||
|
TimeZone = bangkokTimeZone,
|
||||||
|
QueueName = "leave"
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// ตรวจสอบและ mark งาน CheckIn ที่ค้างเกิน 30 นาทีเป็น FAILED - รันทุก 15 นาที
|
// ตรวจสอบและ mark งาน CheckIn ที่ค้างเกิน 30 นาทีเป็น FAILED - รันทุก 15 นาที
|
||||||
// manager.AddOrUpdate("ตรวจสอบงาน CheckIn ที่ค้างเกินเวลา", Job.FromExpression<CheckInJobStatusRepository>(x => x.MarkStaleJobsAsFailedAsync(30)), "*/15 * * * *",
|
// manager.AddOrUpdate("ตรวจสอบงาน CheckIn ที่ค้างเกินเวลา", Job.FromExpression<CheckInJobStatusRepository>(x => x.MarkStaleJobsAsFailedAsync(30)), "*/15 * * * *",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue