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 _locks = new(); public AutoNumberGeneratorService(AppDbContext context) { _context = context; } public async Task 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}"; } }