using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using FrameworkDAL.CustomCode.EntityViewer; using FrameworkDAL.CustomCode.EntityViewer.DTOs; using GB5Shared.DTO.Framework.Login; using GB5Shared.QueryExecutor; using GB5Shared.Resource.Response; using GB5Shared.Validation; using Microsoft.Extensions.Logging; namespace FrameworkBLL.EntityViewer { // ============================================================ // EntityLayoutBLL — Entity Viewer layout registry business logic. // // Same header+version+activate pattern as QualifierBLL, simplified: // no compile step (the admin supplies SnapshotJson directly at save // time), so ActivateLayoutVersion is a pure archive-then-activate // pair, same as QualifierBLL.ActivateQualifierVersion minus the // upfront CompileAsync call. // ============================================================ public class EntityLayoutBLL : IEntityLayoutBLL { private readonly IEntityLayoutDAL _Dal; private readonly IQueryExecutor _QueryExecutor; private readonly IValidation _Validation; private readonly ILogger _Logger; public EntityLayoutBLL( IEntityLayoutDAL dal, IQueryExecutor queryExecutor, IValidation validation, ILogger logger) { _Dal = dal; _QueryExecutor = queryExecutor; _Validation = validation; _Logger = logger; } public async Task SaveLayout(EntityLayoutDTO dto, LoginDTO loginDTO, CancellationToken ct = default) { await _Validation.NotEmpty(dto.EntityCode, nameof(dto.EntityCode)); var isNew = dto.EntityLayoutId == 0; if (isNew) { var id = await _Dal.SaveLayout(dto, loginDTO, ct); return $"{SuccessResponse.SaveSuccessMessage} {id}"; } var rows = await _Dal.UpdateLayout(dto, loginDTO, ct); if (rows == 0) throw new ArgumentException($"EntityLayoutId {dto.EntityLayoutId} was not found (or has been deleted)."); return SuccessResponse.UpdateSuccess; } public async Task DeleteLayout(int entityLayoutId, LoginDTO loginDTO, CancellationToken ct = default) { if (entityLayoutId == 0) throw new ArgumentException("EntityLayoutId is required."); var rows = await _Dal.DeleteLayout(entityLayoutId, loginDTO.UserId, loginDTO, ct); if (rows == 0) throw new ArgumentException($"EntityLayoutId {entityLayoutId} was not found."); return SuccessResponse.DeleteSuccessMessage; } public async Task GetActiveLayout( string entityCode, LoginDTO loginDTO, CancellationToken ct = default) { await _Validation.NotEmpty(entityCode, nameof(entityCode)); return await _Dal.GetActiveLayout(entityCode, loginDTO.ClientId, loginDTO, ct); } public async Task SaveLayoutVersion( EntityLayoutVersionDTO dto, LoginDTO loginDTO, CancellationToken ct = default) { await _Validation.NotEmpty(dto.SnapshotJson ?? string.Empty, nameof(dto.SnapshotJson)); if (dto.EntityLayoutVersionId != 0) { // Editing an existing version in place — only takes effect while it's still // Draft; UPDATE_LAYOUT_VERSION's own WHERE guard is what actually enforces // that, this just turns "0 rows affected" into a real error instead of a // silent no-op. var rows = await _Dal.UpdateLayoutVersion(dto, loginDTO, ct); if (rows == 0) throw new ArgumentException( $"EntityLayoutVersionId {dto.EntityLayoutVersionId} can no longer be edited " + "(already Active or Archived) — save a new version instead."); return SuccessResponse.UpdateSuccess; } if (dto.EntityLayoutId == 0) throw new ArgumentException("EntityLayoutId is required."); var id = await _Dal.SaveLayoutVersion(dto, loginDTO, ct); return $"{SuccessResponse.SaveSuccessMessage} {id}"; } public async Task DeleteLayoutVersion( int entityLayoutVersionId, LoginDTO loginDTO, CancellationToken ct = default) { if (entityLayoutVersionId == 0) throw new ArgumentException("EntityLayoutVersionId is required."); var rows = await _Dal.DeleteLayoutVersion(entityLayoutVersionId, loginDTO.UserId, loginDTO, ct); if (rows == 0) throw new ArgumentException( $"EntityLayoutVersionId {entityLayoutVersionId} can no longer be discarded " + "(already Active or Archived) — it is permanent history."); return SuccessResponse.DeleteSuccessMessage; } public async Task ActivateLayoutVersion( int entityLayoutVersionId, int entityLayoutId, LoginDTO loginDTO, CancellationToken ct = default) { if (entityLayoutVersionId == 0) throw new ArgumentException("EntityLayoutVersionId is required."); if (entityLayoutId == 0) throw new ArgumentException("EntityLayoutId is required."); var trans = await _QueryExecutor.BeginTransactionAsync(loginDTO); try { // Step 1: Archive current Active version (0 rows affected = none active — fine). await _Dal.ArchiveActiveVersion(entityLayoutId, loginDTO.UserId, loginDTO, trans, ct); // Step 2: Activate the target version. await _Dal.ActivateVersion(entityLayoutVersionId, loginDTO.UserId, loginDTO, trans, ct); await _QueryExecutor.CommitAsync(trans); _Logger.LogInformation( "EntityLayoutVersion {VersionId} activated for EntityLayout {LayoutId} by user {UserId}", entityLayoutVersionId, entityLayoutId, loginDTO.UserId); return $"{SuccessResponse.UpdateSuccessMessage} {entityLayoutVersionId}"; } catch { await _QueryExecutor.RollbackAsync(trans); throw; } } public async Task> GetLayoutVersionList( int entityLayoutId, LoginDTO loginDTO, CancellationToken ct = default) => await _Dal.GetLayoutVersionList(entityLayoutId, loginDTO, ct); public async Task> GetLayoutList( bool isComponent, LoginDTO loginDTO, CancellationToken ct = default) => await _Dal.GetLayoutList(isComponent, loginDTO, ct); } }