using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using FrameworkDAL.CustomCode.MetaReport; using FrameworkDAL.DTO.MetaReport; using GB5Shared.DaprCache; using GB5Shared.DTO.Framework.HybridCache; using GB5Shared.DTO.Framework.Login; using GB5Shared.GenerateAutoNumber; using GB5Shared.Resource.Response; using GB5Shared.Telemetry; using static GB5Shared.GB5Constant.Constant; namespace FrameworkBLL.MetaReport { public class MetaReportBLL : IMetaReportBLL { private readonly IMetaReportDAL _metaReportDAL; private readonly AutoNumber _autoNumber; private readonly KeyInvalidate _keyInvalidate; public MetaReportBLL(IMetaReportDAL metaReportDAL, AutoNumber autoNumber, KeyInvalidate keyInvalidate) { _metaReportDAL = metaReportDAL; _autoNumber = autoNumber; _keyInvalidate = keyInvalidate; } public async Task GetMetaReport(int metaReportId, LoginDTO loginDTO, CancellationToken ct) { var dto = await _metaReportDAL.GetMetaReport(metaReportId, loginDTO, ct).ConfigureAwait(false); if (dto is null) return null; dto.Variants = await _metaReportDAL.GetVariantsByMetaReportId(metaReportId, loginDTO, ct).ConfigureAwait(false); dto.Aspects = await _metaReportDAL.GetAspectsByMetaReportId(metaReportId, loginDTO, ct).ConfigureAwait(false); var aspectIds = dto.Aspects.Select(a => a.AspectId).ToList(); var options = await _metaReportDAL.GetOptionsByAspectIds(aspectIds, loginDTO, ct).ConfigureAwait(false); var optionIds = options.Select(o => o.OptionId).ToList(); var links = await _metaReportDAL.GetApplicableVariantsByOptionIds(optionIds, loginDTO, ct).ConfigureAwait(false); var variantIdsByOption = links.GroupBy(l => l.OptionId).ToDictionary(g => g.Key, g => g.Select(l => l.VariantId).ToList()); foreach (var option in options) option.ApplicableVariantIds = variantIdsByOption.TryGetValue(option.OptionId, out var v) ? v : new List(); var optionsByAspect = options.GroupBy(o => o.AspectId).ToDictionary(g => g.Key, g => g.ToList()); foreach (var aspect in dto.Aspects) aspect.Options = optionsByAspect.TryGetValue(aspect.AspectId, out var o) ? o : new List(); return dto; } public async Task> GetMetaReportList(LoginDTO loginDTO, CancellationToken ct) => await _metaReportDAL.GetMetaReportList(loginDTO, ct).ConfigureAwait(false); public async Task SaveMetaReportDefinition(MetaReportDTO dto, LoginDTO loginDTO, CancellationToken ct) { if (dto is null) throw new ArgumentNullException(nameof(dto)); if (string.IsNullOrWhiteSpace(dto.MetaReportName)) throw new ArgumentException("MetaReportName is required.", nameof(dto)); foreach (var aspect in dto.Aspects) if (aspect.AspectType is not ("radio" or "select" or "button" or "checkbox")) throw new ArgumentException($"Unknown AspectType '{aspect.AspectType}' on aspect '{aspect.AspectName}'.", nameof(dto)); var now = DateTime.UtcNow; bool isNew = dto.MetaReportId <= 0; GB5Trace.Step("save-metareport", new { dto.MetaReportId, isNew }); dto.ModifiedById = loginDTO.UserId; dto.ModifiedOn = now; if (isNew) { var auto = await _autoNumber.GetNumberAsync(1, AUTONUMBERCONSTANT.METAREPORT, loginDTO).ConfigureAwait(false); dto.MetaReportId = auto.StartNumber; dto.CreatedById = loginDTO.UserId; dto.CreatedOn = now; } await AssignIds(dto, loginDTO, now).ConfigureAwait(false); try { await _metaReportDAL.SaveMetaReportDefinition(dto, isNew, loginDTO, ct).ConfigureAwait(false); await InvalidateMetaReportCache(dto.MetaReportId, loginDTO).ConfigureAwait(false); return isNew ? $"{SuccessResponse.SaveSuccessMessage} {dto.MetaReportId}" : SuccessResponse.UpdateSuccess; } catch (Exception ex) { GB5Trace.MarkFailed("save-metareport-failed", ex); throw; } } /// Reserves AutoNumber blocks for every new Variant/Aspect/Option in one call per /// entity type (not one call per row), then assigns sequentially while stamping audit /// fields — mirrors MenuGroupBLL.SaveMenuGroupDetail's reservation shape. private async Task AssignIds(MetaReportDTO dto, LoginDTO loginDTO, DateTime now) { int newVariantCount = dto.Variants.Count(v => v.VariantId <= 0); int nextVariantId = 0; if (newVariantCount > 0) nextVariantId = (await _autoNumber.GetNumberAsync(newVariantCount, AUTONUMBERCONSTANT.METAREPORTVARIANT, loginDTO).ConfigureAwait(false)).StartNumber; foreach (var variant in dto.Variants) { variant.MetaReportId = dto.MetaReportId; variant.ModifiedById = loginDTO.UserId; variant.ModifiedOn = now; if (variant.VariantId <= 0) { variant.VariantId = nextVariantId++; variant.CreatedById = loginDTO.UserId; variant.CreatedOn = now; } } int newAspectCount = dto.Aspects.Count(a => a.AspectId <= 0); int nextAspectId = 0; if (newAspectCount > 0) nextAspectId = (await _autoNumber.GetNumberAsync(newAspectCount, AUTONUMBERCONSTANT.METAREPORTASPECT, loginDTO).ConfigureAwait(false)).StartNumber; int newOptionCount = dto.Aspects.SelectMany(a => a.Options).Count(o => o.OptionId <= 0); int nextOptionId = 0; if (newOptionCount > 0) nextOptionId = (await _autoNumber.GetNumberAsync(newOptionCount, AUTONUMBERCONSTANT.METAREPORTASPECTOPTION, loginDTO).ConfigureAwait(false)).StartNumber; foreach (var aspect in dto.Aspects) { aspect.MetaReportId = dto.MetaReportId; aspect.ModifiedById = loginDTO.UserId; aspect.ModifiedOn = now; if (aspect.AspectId <= 0) { aspect.AspectId = nextAspectId++; aspect.CreatedById = loginDTO.UserId; aspect.CreatedOn = now; } foreach (var option in aspect.Options) { option.AspectId = aspect.AspectId; option.ModifiedById = loginDTO.UserId; option.ModifiedOn = now; if (option.OptionId <= 0) { option.OptionId = nextOptionId++; option.CreatedById = loginDTO.UserId; option.CreatedOn = now; } } } } public async Task DeleteMetaReport(int metaReportId, LoginDTO loginDTO, CancellationToken ct) { try { GB5Trace.Step("delete-metareport", new { metaReportId }); await _metaReportDAL.DeleteMetaReport(metaReportId, loginDTO, ct).ConfigureAwait(false); await InvalidateMetaReportCache(metaReportId, loginDTO).ConfigureAwait(false); return SuccessResponse.DeleteSuccessMessage; } catch (Exception ex) { GB5Trace.MarkFailed("delete-metareport-failed", ex); throw; } } private async Task InvalidateMetaReportCache(int metaReportId, LoginDTO loginDTO) { var cacheKey = CacheKeyGenerator.KeyGeneration(metaReportId, EntityConstant.OBJECTMETAREPORT, CacheKeyLevel.CLIENT_LEVEL, string.Empty, loginDTO); await _keyInvalidate.AllInvalidateCache(cacheKey).ConfigureAwait(false); } } }