using System.ComponentModel.DataAnnotations; using AdminDAL.CustomCode.FormTemplate; using AdminDAL.CustomCode.TemplateData; using AdminDAL.DTO.TemplateData; using GB5Shared.DTO.Framework.Login; using GB5Shared.EventLogPublish; using GB5Shared.Resource.Response; using GB5Shared.Telemetry; using GB5Shared.Validation; using Microsoft.Extensions.Logging; using static GB5Shared.GB5Constant.Constant; namespace AdminBLL.TemplateData { // GB5-native rebuild of the legacy GB4 AdminBLL.TemplateData. The legacy source // (~/Downloads/GB4Solution/BLL/AdminBLL/TemplateData/TemplateDataBLL.cs) is reference-only — // see /Users/venkatv/.claude/plans/we-have-done-a-adaptive-stroustrup.md. public class TemplateDataBLL : ITemplateDataBLL { private readonly ITemplateDataDAL _DAL; private readonly IFormTemplateDAL _FormTemplateDAL; private readonly IValidation _Validation; private readonly EventLogPublish _EventLog; private readonly ILogger _Logger; public TemplateDataBLL( ITemplateDataDAL DAL, IFormTemplateDAL FormTemplateDAL, IValidation Validation, EventLogPublish EventLog, ILogger Logger) { _DAL = DAL; _FormTemplateDAL = FormTemplateDAL; _Validation = Validation; _EventLog = EventLog; _Logger = Logger; } public async Task GetTemplateData(int TemplateDataId, LoginDTO LoginDTO, CancellationToken ct) { GB5Trace.Step("get-templatedata", new { TemplateDataId }); return await _DAL.GetTemplateData(TemplateDataId, LoginDTO, ct).ConfigureAwait(false); } public async Task GetSelectListTemplateData(int FormTemplateId, LoginDTO LoginDTO, CancellationToken ct) => await _DAL.GetSelectListTemplateData(FormTemplateId, LoginDTO, ct).ConfigureAwait(false); public async Task SaveTemplateData(TemplateDataDTO TemplateDataDTO, LoginDTO LoginDTO, CancellationToken ct) { ArgumentNullException.ThrowIfNull(TemplateDataDTO); var isNew = TemplateDataDTO.TemplateDataId <= 0; GB5Trace.Step("validate-templatedata", new { TemplateDataDTO.FormTemplateId }); try { await _Validation.Range(1, int.MaxValue, TemplateDataDTO.FormTemplateId, nameof(TemplateDataDTO.FormTemplateId)).ConfigureAwait(false); var formTemplate = await _FormTemplateDAL.GetFormTemplateByCode( TemplateDataDTO.FormTemplateCode, LoginDTO, ct).ConfigureAwait(false); if (formTemplate is null) throw new ValidationException( $"FormTemplateCode '{TemplateDataDTO.FormTemplateCode}' does not reference an existing form."); // Resolved server-side from FormTemplateCode, never trusted from the client — // the caller only needs to know the form's own code, not its internal id, and a // client-submitted FormTemplateId that didn't match the code would either violate // the FK constraint or (worse) silently attach this row to the wrong form. TemplateDataDTO.FormTemplateId = formTemplate.FormTemplateId; TemplateDataDTO.TemplateDataModifiedById = LoginDTO.UserId; TemplateDataDTO.TemplateDataModifiedOn = DateTime.UtcNow; // Real TENANTID column on TTEMPLATEDATA (FK-constrained) — resolved server-side, // never trusted from the client. TemplateDataDTO.TenantId = LoginDTO.ClientId; GB5Trace.Step("save-templatedata", new { TemplateDataDTO.TemplateDataId, isNew }); if (isNew) { TemplateDataDTO.TemplateDataCreatedById = LoginDTO.UserId; TemplateDataDTO.TemplateDataCreatedOn = DateTime.UtcNow; TemplateDataDTO.TemplateDataStatus = 1; // TEMPLATEDATAID is a real IDENTITY column (confirmed against a live // database) — the DB assigns it, not an AutoNumber allocation. TemplateDataDTO.TemplateDataId = await _DAL.SaveTemplateData(TemplateDataDTO, LoginDTO, ct).ConfigureAwait(false); } else { await _DAL.UpdateTemplateData(TemplateDataDTO, LoginDTO, ct).ConfigureAwait(false); } // "genericTable" (Dynamic Table) forms also get the submitted values written // into D_ — see TemplateDataDAL.PostDataToDynamicTable. // TEMPLATEDATADATA (the JSON blob) is populated either way, above. if (formTemplate.FormTemplateDataTableType == 1) { GB5Trace.Step("post-data-to-dynamic-table", new { formTemplate.FormTemplateCode }); await _DAL.PostDataToDynamicTable(TemplateDataDTO, formTemplate, LoginDTO, ct).ConfigureAwait(false); } GB5Trace.Step("event-publish", new { EventTypeId = EventTypeConstant.SAVETEMPLATEDATAEVENTTYPEID }); await _EventLog.PublishEventLogAsync( isNew ? "TemplateData Created" : "TemplateData Updated", TemplateDataDTO, isNew ? EventTypeConstant.SAVETEMPLATEDATAEVENTTYPEID : EventTypeConstant.UPDATETEMPLATEDATAEVENTTYPEID, TemplateDataDTO.TemplateDataId, LoginDTO, ct: ct).ConfigureAwait(false); return isNew ? $"{SuccessResponse.SaveSuccessMessage} {TemplateDataDTO.TemplateDataId}" : $"{SuccessResponse.UpdateSuccessMessage} {TemplateDataDTO.TemplateDataId}"; } catch (ValidationException vex) { GB5Trace.MarkFailed("save-templatedata-validation-failed", vex); throw new Exception(vex.Message); } catch (Exception ex) { GB5Trace.MarkFailed("save-templatedata-failed", ex); _Logger.LogError(ex, "SaveTemplateData failed for FormTemplateId {Id}", TemplateDataDTO.FormTemplateId); // No AutoNumber rollback needed — TEMPLATEDATAID is a real IDENTITY column. throw; } } public async Task DeleteTemplateData(int TemplateDataId, LoginDTO LoginDTO, CancellationToken ct) { GB5Trace.Step("delete-templatedata", new { TemplateDataId }); try { var result = await _DAL.DeleteTemplateData(TemplateDataId, LoginDTO, ct).ConfigureAwait(false); GB5Trace.Step("event-publish", new { EventTypeId = EventTypeConstant.DELETETEMPLATEDATAEVENTTYPEID }); await _EventLog.PublishEventLogAsync( "TemplateData Deleted", new { TemplateDataId }, EventTypeConstant.DELETETEMPLATEDATAEVENTTYPEID, TemplateDataId, LoginDTO, ct: ct).ConfigureAwait(false); return result; } catch (Exception ex) { GB5Trace.MarkFailed("delete-templatedata-failed", ex); _Logger.LogError(ex, "DeleteTemplateData failed for TemplateDataId {Id}", TemplateDataId); throw; } } } }