initial commit
This commit is contained in:
@@ -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}";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user