using System.Text.Json; using FLSDAL.CustomCode.Registration; using FLSDAL.DTO.Registration; using GB5Shared.DaprCache; using GB5Shared.DTO.Framework.Criteria; using GB5Shared.DTO.Framework.Login; using GB5Shared.EventLogPublish; using GB5Shared.GenerateAutoNumber; using GB5Shared.Resource.Response; using GB5Shared.Telemetry; using GB5Shared.Validation; using Microsoft.Extensions.Logging; using static GB5Shared.GB5Constant.Constant; namespace FLSBLL.Registration; public class FlsRegistrationBLL : IFlsRegistrationBLL { private readonly IFlsRegistrationDAL _dal; private readonly IValidation _validation; private readonly AutoNumber _autoNumber; private readonly KeyInvalidate _keyInvalidate; private readonly EventLogPublish _eventLog; private readonly ILogger _logger; public FlsRegistrationBLL( IFlsRegistrationDAL dal, IValidation validation, AutoNumber autoNumber, KeyInvalidate keyInvalidate, EventLogPublish eventLog, ILogger logger) { _dal = dal; _validation = validation; _autoNumber = autoNumber; _keyInvalidate = keyInvalidate; _eventLog = eventLog; _logger = logger; } public async Task CreateRegistrationAsync(CreateFlsRegistrationRequest req, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("validate-fls-registration", new { req.RegistrationCode }); await _validation.NotEmpty(req.RegistrationCode, nameof(req.RegistrationCode)).ConfigureAwait(false); await _validation.NotEmpty(req.RegistrationName, nameof(req.RegistrationName)).ConfigureAwait(false); if (req.FormTemplateId == 0) return "Form template is required."; if (req.TokenExpiryHours <= 0) return "Token expiry hours must be greater than zero."; // 4 = ExternalModule (another internal module owns the form and reports completion, // e.g. TMS's FLS-as-coordinator assessment delivery) — widened alongside the // CK_MFLSREGISTRATION_COMPLETION check constraint. if (req.CompletionMethod is < 0 or > 4) return "Invalid completion method."; var regCacheKey = $"FlsRegistration:{login.ClientId}"; // PK convention: FlsRegistrationId == 0 → insert (new AutoNumber); non-zero → update existing row. if (req.FlsRegistrationId == 0) { bool duplicate = await _dal.CodeExistsAsync(req.RegistrationCode, 0, login, ct).ConfigureAwait(false); if (duplicate) return $"Registration code '{req.RegistrationCode}' already exists."; var autoNum = await _autoNumber.GetNumberAsync(1, "FLSREGISTRATION", login).ConfigureAwait(false); int flsRegistrationId = autoNum.StartNumber; GB5Trace.Step("save-fls-registration", new { req.RegistrationCode }); var id = await _dal.CreateAsync( flsRegistrationId, req.RegistrationCode, req.RegistrationName, req.FormTemplateId, req.ModuleId, req.CompletionMethod, req.AllowDraftResponse, req.AllowMultiAttempt, req.IsMandatory, req.TokenExpiryHours, req.TriggeredBy, req.DefaultEipTemplCode, req.WebhookPath, login, ct).ConfigureAwait(false); GB5Trace.Step("event-publish", new { EventTypeId = EventTypeConstant.SAVEDOCUMENTSET }); await _eventLog.PublishEventLogAsync( "FLS Registration Created", new { req.RegistrationCode, id }, EventTypeConstant.SAVEDOCUMENTSET, id, login, ct: ct).ConfigureAwait(false); await _keyInvalidate.InvalidateCache(regCacheKey).ConfigureAwait(false); return $"{SuccessResponse.SaveSuccessMessage} {id}"; } else { bool duplicate = await _dal.CodeExistsAsync(req.RegistrationCode, req.FlsRegistrationId, login, ct).ConfigureAwait(false); if (duplicate) return $"Registration code '{req.RegistrationCode}' already exists."; GB5Trace.Step("update-fls-registration", new { req.FlsRegistrationId }); bool updated = await _dal.UpdateAsync( req.FlsRegistrationId, req.RegistrationName, req.FormTemplateId, req.ModuleId, req.CompletionMethod, req.AllowDraftResponse, req.AllowMultiAttempt, req.IsMandatory, req.TokenExpiryHours, req.TriggeredBy, req.DefaultEipTemplCode, req.WebhookPath, login, ct).ConfigureAwait(false); if (!updated) return $"Registration {req.FlsRegistrationId} not found."; await _keyInvalidate.InvalidateCache(regCacheKey).ConfigureAwait(false); return $"{SuccessResponse.UpdateSuccess} {req.FlsRegistrationId}"; } } catch (Exception ex) { GB5Trace.MarkFailed("save-fls-registration-failed", ex); _logger.LogError(ex, "CreateRegistration failed for code {Code}", req.RegistrationCode); throw; } } public async Task GetRegistrationAsync(int flsRegistrationId, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("get-fls-registration", new { flsRegistrationId }); var reg = await _dal.GetByIdAsync(flsRegistrationId, login, ct).ConfigureAwait(false); var rules = await _dal.GetReminderRulesAsync(flsRegistrationId, login, ct).ConfigureAwait(false); return JsonSerializer.Serialize(new { Registration = reg, ReminderRules = rules }); } catch (Exception ex) { GB5Trace.MarkFailed("get-fls-registration-failed", ex); _logger.LogError(ex, "GetRegistration failed for id {Id}", flsRegistrationId); throw; } } public async Task GetAccessRulesAsync(int flsRegistrationId, LoginDTO login, CancellationToken ct) { try { var rules = await _dal.GetAccessRulesAsync(flsRegistrationId, login, ct).ConfigureAwait(false); return JsonSerializer.Serialize(rules); } catch (Exception ex) { GB5Trace.MarkFailed("get-access-rules-failed", ex); _logger.LogError(ex, "GetAccessRules failed for regId {Id}", flsRegistrationId); throw; } } public async Task GetAccessRuleAsync(int accessRuleId, LoginDTO login, CancellationToken ct) { try { var rule = await _dal.GetAccessRuleByIdAsync(accessRuleId, login, ct).ConfigureAwait(false); return JsonSerializer.Serialize(rule); } catch (Exception ex) { GB5Trace.MarkFailed("get-access-rule-failed", ex); _logger.LogError(ex, "GetAccessRule failed for id {Id}", accessRuleId); throw; } } public async Task SaveAccessRuleAsync(SaveAccessRuleRequest req, LoginDTO login, CancellationToken ct) { try { // PK convention: AccessRuleId == 0 → insert (new AutoNumber); non-zero → update existing row. if (req.AccessRuleId == 0) { GB5Trace.Step("save-access-rule", new { req.FlsRegistrationId, req.PrincipalId }); var autoNum = await _autoNumber.GetNumberAsync(1, "FLSACCESSRULE", login).ConfigureAwait(false); int ruleId = autoNum.StartNumber; await _dal.InsertAccessRuleAsync(ruleId, req, login, ct).ConfigureAwait(false); return $"{SuccessResponse.SaveSuccessMessage} {ruleId}"; } else { GB5Trace.Step("update-access-rule", new { req.AccessRuleId }); bool updated = await _dal.UpdateAccessRuleAsync(req, login, ct).ConfigureAwait(false); if (!updated) return $"Access rule {req.AccessRuleId} not found."; return $"{SuccessResponse.UpdateSuccess} {req.AccessRuleId}"; } } catch (Exception ex) { GB5Trace.MarkFailed("save-access-rule-failed", ex); _logger.LogError(ex, "SaveAccessRule failed for regId {Id}", req.FlsRegistrationId); throw; } } public async Task DeleteAccessRuleAsync(int accessRuleId, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("delete-access-rule", new { accessRuleId }); bool deleted = await _dal.DeleteAccessRuleAsync(accessRuleId, login, ct).ConfigureAwait(false); return deleted ? SuccessResponse.DeleteSuccessMessage : $"Access rule {accessRuleId} not found."; } catch (Exception ex) { GB5Trace.MarkFailed("delete-access-rule-failed", ex); _logger.LogError(ex, "DeleteAccessRule failed for id {Id}", accessRuleId); throw; } } public async Task> GetReminderRuleListAsync(int flsRegistrationId, LoginDTO login, CancellationToken ct) { try { return await _dal.GetReminderRulesAsync(flsRegistrationId, login, ct).ConfigureAwait(false); } catch (Exception ex) { GB5Trace.MarkFailed("get-reminder-rule-list-failed", ex); _logger.LogError(ex, "GetReminderRuleList failed for regId {Id}", flsRegistrationId); throw; } } public async Task SaveReminderRuleAsync(SaveFlsReminderRuleRequest req, LoginDTO login, CancellationToken ct) { try { // PK convention: ReminderRuleId == 0 → insert (new AutoNumber); non-zero → update existing row. if (req.ReminderRuleId == 0) { GB5Trace.Step("save-reminder-rule", new { req.FlsRegistrationId, req.RuleName }); var autoNum = await _autoNumber.GetNumberAsync(1, "FLSREMINDERRULE", login).ConfigureAwait(false); int ruleId = autoNum.StartNumber; await _dal.InsertReminderRuleAsync(ruleId, req, login, ct).ConfigureAwait(false); return $"{SuccessResponse.SaveSuccessMessage} {ruleId}"; } else { GB5Trace.Step("update-reminder-rule", new { req.ReminderRuleId }); bool updated = await _dal.UpdateReminderRuleAsync(req, login, ct).ConfigureAwait(false); if (!updated) return $"Reminder rule {req.ReminderRuleId} not found."; return $"{SuccessResponse.UpdateSuccess} {req.ReminderRuleId}"; } } catch (Exception ex) { GB5Trace.MarkFailed("save-reminder-rule-failed", ex); _logger.LogError(ex, "SaveReminderRule failed for regId {Id}", req.FlsRegistrationId); throw; } } public async Task DeleteReminderRuleAsync(int reminderRuleId, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("delete-reminder-rule", new { reminderRuleId }); bool deleted = await _dal.DeleteReminderRuleAsync(reminderRuleId, login, ct).ConfigureAwait(false); return deleted ? SuccessResponse.DeleteSuccessMessage : $"Reminder rule {reminderRuleId} not found."; } catch (Exception ex) { GB5Trace.MarkFailed("delete-reminder-rule-failed", ex); _logger.LogError(ex, "DeleteReminderRule failed for id {Id}", reminderRuleId); throw; } } public async Task DeleteRegistrationAsync(int flsRegistrationId, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("delete-fls-registration", new { flsRegistrationId }); bool deleted = await _dal.DeleteAsync(flsRegistrationId, login, ct).ConfigureAwait(false); if (!deleted) return $"Registration {flsRegistrationId} not found."; await _keyInvalidate.InvalidateCache($"FlsRegistration:{login.ClientId}").ConfigureAwait(false); return SuccessResponse.DeleteSuccessMessage; } catch (Exception ex) { GB5Trace.MarkFailed("delete-fls-registration-failed", ex); _logger.LogError(ex, "DeleteRegistration failed for id {Id}", flsRegistrationId); throw; } } public async Task> GetListAsync( int firstNumber, int maxResult, CriteriaDTO criteriaDTO, LoginDTO login) { try { if (firstNumber <= 0 && maxResult <= 0) { firstNumber = -1; maxResult = -1; } return await _dal.GetListAsync(firstNumber, maxResult, criteriaDTO, login).ConfigureAwait(false); } catch (Exception ex) { GB5Trace.MarkFailed("get-fls-registration-list-failed", ex); _logger.LogError(ex, "GetList failed for FlsRegistration"); throw; } } public async Task> GetSelectListFlsRegistration( int firstNumber, int maxResult, CriteriaDTO criteriaDTO, LoginDTO login) { try { if (firstNumber <= 0 && maxResult <= 0) { firstNumber = -1; maxResult = -1; } return await _dal.GetSelectListFlsRegistration(firstNumber, maxResult, criteriaDTO, login).ConfigureAwait(false); } catch (Exception ex) { GB5Trace.MarkFailed("get-selectlist-flsregistration-failed", ex); _logger.LogError(ex, "GetSelectListFlsRegistration failed"); throw; } } public async Task> GetSelectListAccessRule( int flsRegistrationId, int firstNumber, int maxResult, CriteriaDTO criteriaDTO, LoginDTO login) { try { if (firstNumber <= 0 && maxResult <= 0) { firstNumber = -1; maxResult = -1; } return await _dal.GetSelectListAccessRule(flsRegistrationId, firstNumber, maxResult, criteriaDTO, login).ConfigureAwait(false); } catch (Exception ex) { GB5Trace.MarkFailed("get-selectlist-accessrule-failed", ex); _logger.LogError(ex, "GetSelectListAccessRule failed for regId {Id}", flsRegistrationId); throw; } } }