using FrameworkBLL.MenuGroup; using FrameworkDAL.CustomCode.MenuSet; using FrameworkDAL.DTO.MenuSet; using GB5Shared.DaprCache; using GB5Shared.DTO.Framework.Criteria; using GB5Shared.DTO.Framework.HybridCache; using GB5Shared.DTO.Framework.Login; using GB5Shared.GenerateAutoNumber; using GB5Shared.Resource.Response; using GB5Shared.Telemetry; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using static GB5Shared.GB5Constant.Constant; namespace FrameworkBLL.MenuSet { public class MenuSetBLL : IMenuSetBLL { private readonly IMenuSetDAL _menuSetDAL; private readonly IMenuGroupBLL _menuGroupBLL; private readonly AutoNumber _autoNumber; private readonly KeyInvalidate _keyInvalidate; public MenuSetBLL(IMenuSetDAL menuSetDAL, IMenuGroupBLL menuGroupBLL, AutoNumber autoNumber, KeyInvalidate keyInvalidate) { _menuSetDAL = menuSetDAL; _menuGroupBLL = menuGroupBLL; _autoNumber = autoNumber; _keyInvalidate = keyInvalidate; } public async Task GetMenuSet(int menuSetId, LoginDTO LoginDTO) { try { return await _menuSetDAL.GetMenuSet(menuSetId, LoginDTO); } catch (Exception) { throw; } } public async Task GetSelectListMenuSet(CriteriaDTO criteriaDTO, LoginDTO LoginDTO) { try { return await _menuSetDAL.GetSelectListMenuSet(criteriaDTO, LoginDTO); } catch (Exception) { throw; } } /// /// Diff-based save of a MenuSet's per-client detail overrides (MMENUSETDETAIL) — /// mirrors MenuGroupBLL.SaveMenuGroupDetail's shape. Every row that sets MenuGroupId /// (a re-filing override) is validated against IMenuGroupBLL.ValidateMenuGroupVisibility /// first — a plain FK can't express "shared or private-to-this-set", so this is the one /// place that rule is actually enforced, closing the gap left when /// ValidateMenuGroupVisibility was first built with no caller. /// public async Task SaveMenuSetDetail(int menuSetId, List details, LoginDTO loginDTO, CancellationToken ct) { if (details is null) throw new ArgumentNullException(nameof(details)); foreach (var detail in details.Where(d => d.MenuGroupId.HasValue)) await _menuGroupBLL.ValidateMenuGroupVisibility(detail.MenuGroupId!.Value, menuSetId, loginDTO, ct).ConfigureAwait(false); var now = DateTime.UtcNow; int newCount = details.Count(d => d.MenuSetDetailId <= 0); int nextNewId = 0; if (newCount > 0) { var auto = await _autoNumber.GetNumberAsync(newCount, AUTONUMBERCONSTANT.MENUSETDETAIL, loginDTO).ConfigureAwait(false); nextNewId = auto.StartNumber; } foreach (var detail in details) { if (detail.MenuSetDetailId <= 0) detail.MenuSetDetailId = nextNewId++; } try { GB5Trace.Step("save-menusetdetail", new { menuSetId, count = details.Count }); await _menuSetDAL.SaveMenuSetDetail(menuSetId, details, loginDTO).ConfigureAwait(false); // Invalidates GetMenuGroupTree's cache (OBJECTMENUGROUP, keyed on MenuSetId) — // that's the read path these overrides actually affect, not a MenuSet-domain // cache of its own. See MenuGroupBLL's own caching remarks for the same key shape. var cacheKey = CacheKeyGenerator.KeyGeneration(menuSetId, EntityConstant.OBJECTMENUGROUP, CacheKeyLevel.CLIENT_LEVEL, string.Empty, loginDTO); await _keyInvalidate.AllInvalidateCache(cacheKey).ConfigureAwait(false); return SuccessResponse.UpdateSuccess; } catch (Exception ex) { GB5Trace.MarkFailed("save-menusetdetail-failed", ex); throw; } } public async Task SaveMenuSet(MenuSetDTO dto, LoginDTO loginDTO, CancellationToken ct) { if (dto is null) throw new ArgumentNullException(nameof(dto)); if (string.IsNullOrWhiteSpace(dto.MenuSetName)) throw new ArgumentException("MenuSetName is required.", nameof(dto)); if (dto.PresentationType != 0 && dto.MenuGroupId == -1) throw new ArgumentException("MenuGroupId must be set when PresentationType is not 0 (legacy).", nameof(dto)); var now = DateTime.UtcNow; bool isNew = dto.MenuSetId <= 0; GB5Trace.Step("save-menuset", new { dto.MenuSetId, isNew }); dto.MenuSetModifiedById = loginDTO.UserId; dto.MenuSetModifiedOn = now; if (isNew) { var auto = await _autoNumber.GetNumberAsync(1, AUTONUMBERCONSTANT.MENUSET, loginDTO).ConfigureAwait(false); dto.MenuSetId = auto.StartNumber; dto.MenuSetCreatedById = loginDTO.UserId; dto.MenuSetCreatedOn = now; await _menuSetDAL.SaveMenuSet(dto, loginDTO).ConfigureAwait(false); } else { await _menuSetDAL.UpdateMenuSet(dto, loginDTO).ConfigureAwait(false); } return isNew ? $"{SuccessResponse.SaveSuccessMessage} {dto.MenuSetId}" : SuccessResponse.UpdateSuccess; } public async Task DeleteMenuSet(int menuSetId, LoginDTO loginDTO, CancellationToken ct) { GB5Trace.Step("delete-menuset", new { menuSetId }); await _menuSetDAL.DeleteMenuSet(menuSetId, loginDTO).ConfigureAwait(false); return SuccessResponse.DeleteSuccessMessage; } } }