using System; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading; using System.Threading.Tasks; using GB5Shared.DTO.Framework.AutoNumber; using GB5Shared.DTO.Framework.Login; using GB5Shared.GenerateAutoNumber; using GB5Shared.Resource.Response; using GB5Shared.Validation; using Microsoft.Extensions.Logging; using MMDAL.CustomCode.ConfigVersion; using MMDAL.DTO.ConfigVersion; namespace MMBLL.ConfigVersion { public class ConfigVersionBLL : IConfigVersionBLL { private readonly IConfigVersionDAL _dal; private readonly AutoNumber _autoNumber; private readonly IValidation _validation; private readonly ILogger _logger; public ConfigVersionBLL( IConfigVersionDAL dal, AutoNumber autoNumber, IValidation validation, ILogger logger) { _dal = dal; _autoNumber = autoNumber; _validation = validation; _logger = logger; } public async Task GetConfigVersion(int configVersionId, LoginDTO login, CancellationToken ct) { try { return await _dal.GetConfigVersion(configVersionId, login, ct).ConfigureAwait(false); } catch (Exception) { throw; } } public async Task GetSelectListConfigVersion(LoginDTO login, CancellationToken ct) { try { return await _dal.GetSelectListConfigVersion(login, ct).ConfigureAwait(false); } catch (Exception) { throw; } } public async Task SaveConfigVersion(ConfigVersionDTO dto, LoginDTO login, CancellationToken ct) { if (dto == null) throw new ArgumentNullException(nameof(dto)); AutoNumberDTO? autoNumberDTO = null; try { await _validation.NotEmpty(dto.ConfigVersionName, nameof(dto.ConfigVersionName)).ConfigureAwait(false); if (dto.EffectiveFrom == default) throw new ValidationException("EffectiveFrom is required."); dto.ModifiedById = login.UserId; dto.ModifiedOn = DateTime.UtcNow; dto.TenantId = login.ClientId; bool isNew = dto.ConfigVersionId == 0; if (isNew) { autoNumberDTO = await _autoNumber.GetAutoNumber(1, "CONFIGVERSION", login).ConfigureAwait(false); dto.ConfigVersionId = autoNumberDTO.StartNumber; dto.CreatedById = login.UserId; dto.CreatedOn = DateTime.UtcNow; dto.Status = 0; // Draft — activation is a separate Publish step dto.Version = 1; await _dal.SaveConfigVersion(dto, login, ct).ConfigureAwait(false); } else { await _dal.UpdateConfigVersion(dto, login, ct).ConfigureAwait(false); } await RefreshMembersAsync(dto, login, ct).ConfigureAwait(false); return isNew ? $"{SuccessResponse.SaveSuccessMessage} {dto.ConfigVersionId}" : $"{SuccessResponse.UpdateSuccessMessage} {dto.ConfigVersionId}"; } catch (ValidationException vex) { _logger.LogWarning("SaveConfigVersion validation: {Message}", vex.Message); throw; } catch (Exception ex) { _logger.LogError(ex, "SaveConfigVersion failed for ConfigVersionName {Name}", dto?.ConfigVersionName); throw; } } public async Task PublishConfigVersion(int configVersionId, LoginDTO login, CancellationToken ct) { try { // Retrieve to get ConfigType and EffectiveFrom for archive boundary var json = await _dal.GetConfigVersion(configVersionId, login, ct).ConfigureAwait(false); if (string.IsNullOrWhiteSpace(json) || json == "null") throw new ValidationException($"ConfigVersion {configVersionId} not found."); // Archive all other Active versions for the same CONFIGTYPE before activating // ConfigVersionQB.ARCHIVE_PREVIOUS_ACTIVE sets STATUS=2 + EFFECTIVETO on all others // We pass configVersionId so it is excluded from archiving. // configType=0 is the only type currently used. await _dal.ArchivePreviousActive(configVersionId, configType: 0, effectiveFrom: DateTime.UtcNow.Date, login, ct) .ConfigureAwait(false); // Activate this version: UPDATE STATUS=1 var activateDto = new ConfigVersionDTO { ConfigVersionId = configVersionId, Status = 1, // Active EffectiveFrom = DateTime.UtcNow.Date, ModifiedById = login.UserId, ModifiedOn = DateTime.UtcNow, TenantId = login.ClientId }; await _dal.UpdateConfigVersion(activateDto, login, ct).ConfigureAwait(false); return $"{SuccessResponse.UpdateSuccessMessage} {configVersionId}"; } catch (ValidationException vex) { _logger.LogWarning("PublishConfigVersion validation: {Message}", vex.Message); throw; } catch (Exception ex) { _logger.LogError(ex, "PublishConfigVersion failed for ConfigVersionId {Id}", configVersionId); throw; } } public async Task DeleteConfigVersion(int configVersionId, LoginDTO login, CancellationToken ct) { try { // DELETE_CONFIGVERSION is guarded with AND STATUS=0; rowsAffected=0 means not Draft await _dal.DeleteMembers(configVersionId, login, ct).ConfigureAwait(false); int rows = await _dal.DeleteConfigVersion(configVersionId, login, ct).ConfigureAwait(false); if (rows == 0) throw new ValidationException("Cannot delete — only Draft (Status=0) versions may be deleted."); return SuccessResponse.DeleteSuccessMessage; } catch (ValidationException vex) { _logger.LogWarning("DeleteConfigVersion validation: {Message}", vex.Message); throw; } catch (Exception ex) { _logger.LogError(ex, "DeleteConfigVersion failed for ConfigVersionId {Id}", configVersionId); throw; } } // ── Helpers ────────────────────────────────────────────────────────────── private async Task RefreshMembersAsync(ConfigVersionDTO dto, LoginDTO login, CancellationToken ct) { // Delete existing members first (idempotent replace) await _dal.DeleteMembers(dto.ConfigVersionId, login, ct).ConfigureAwait(false); var members = dto.Members != null && dto.Members.Count > 0 ? dto.Members : (await _dal.GetFormulasFromActiveCharges(login, ct).ConfigureAwait(false)).ToList(); int slNo = 1; foreach (var member in members) { member.ConfigVersionId = dto.ConfigVersionId; member.ConfigVersionMemberId = dto.ConfigVersionId * 1000 + slNo; member.SlNo = slNo; member.MemberType = 0; // Formula member.TenantId = login.ClientId; await _dal.SaveMember(member, login, ct).ConfigureAwait(false); slNo++; } } } }