using System.ComponentModel.DataAnnotations; using GB5Shared.DaprCache; using GB5Shared.DTO.Framework.AutoNumber; using GB5Shared.DTO.Framework.Criteria; using GB5Shared.DTO.Framework.Login; using GB5Shared.EntityHandler; using GB5Shared.GenerateAutoNumber; using GB5Shared.Resource.Response; using GB5Shared.Telemetry; using GB5Shared.Validation; using CRMSalesDAL.CustomCode.LeadScoringConfig; using CRMSalesDAL.DTO.LeadScoringConfig; using Microsoft.Extensions.Logging; using static GB5Shared.GB5Constant.Constant; namespace CRMSalesBLL.LeadScoringConfig; public class LeadScoringConfigBLL : ILeadScoringConfigBLL { private readonly ILeadScoringConfigDAL _LeadScoringConfigDAL; private readonly AutoNumber _AutoNumber; private readonly IValidation _Validation; private readonly KeyInvalidate _KeyInvalidate; private readonly BaseEntityAppService _BaseEntityAppService; private readonly ILogger _Logger; public LeadScoringConfigBLL( ILeadScoringConfigDAL leadScoringConfigDAL, AutoNumber autoNumber, IValidation validation, KeyInvalidate keyInvalidate, BaseEntityAppService baseEntityAppService, ILogger logger) { _LeadScoringConfigDAL = leadScoringConfigDAL; _AutoNumber = autoNumber; _Validation = validation; _KeyInvalidate = keyInvalidate; _BaseEntityAppService = baseEntityAppService; _Logger = logger; } public async Task GetLeadScoringConfig(int leadScoringConfigId, LoginDTO loginDTO, CancellationToken ct) { try { return await _LeadScoringConfigDAL.GetLeadScoringConfig(leadScoringConfigId, loginDTO, ct).ConfigureAwait(false); } catch (Exception ex) { GB5Trace.MarkFailed("get-leadscoringconfig-failed", ex); _Logger.LogError(ex, "GetLeadScoringConfig failed for LeadScoringConfigId {LeadScoringConfigId}", leadScoringConfigId); throw; } } public async Task SaveLeadScoringConfig(LeadScoringConfigDTO leadScoringConfigDTO, LoginDTO loginDTO, CancellationToken ct) { if (leadScoringConfigDTO == null) throw new ArgumentNullException(nameof(leadScoringConfigDTO)); AutoNumberDTO? autoNumberDTO = null; try { GB5Trace.Step("validate-leadscoringconfig", new { leadScoringConfigDTO.LeadScoringConfigId }); await _Validation.NotEmpty(leadScoringConfigDTO.LeadScoringConfigCode, nameof(leadScoringConfigDTO.LeadScoringConfigCode)); await _Validation.NotEmpty(leadScoringConfigDTO.LeadScoringConfigName, nameof(leadScoringConfigDTO.LeadScoringConfigName)); // The 4 weights must sum to 100 — brief's explicit rule. No generic "sum" check exists // on IValidation, so this is enforced directly (same style as other cross-field rules // that don't map to a single-field Range/NotEmpty check elsewhere in this repo). decimal weightSum = leadScoringConfigDTO.DemographicFitWeightPercent + leadScoringConfigDTO.BehavioralSignalsWeightPercent + leadScoringConfigDTO.EngagementLevelWeightPercent + leadScoringConfigDTO.DealReadinessWeightPercent; if (weightSum != 100m) throw new ValidationException( $"The 4 scoring weights must sum to 100 (got {weightSum})."); leadScoringConfigDTO.ModifiedById = loginDTO.UserId; leadScoringConfigDTO.ModifiedOn = DateTime.UtcNow; bool isNew = leadScoringConfigDTO.LeadScoringConfigId == 0; if (isNew) { autoNumberDTO = await _AutoNumber.GetNumberAsync(1, "LEADSCORINGCONFIG", loginDTO).ConfigureAwait(false); leadScoringConfigDTO.LeadScoringConfigId = autoNumberDTO.StartNumber; leadScoringConfigDTO.CreatedById = loginDTO.UserId; leadScoringConfigDTO.CreatedOn = DateTime.UtcNow; } GB5Trace.Step("save-leadscoringconfig", new { leadScoringConfigDTO.LeadScoringConfigId, isNew }); await _BaseEntityAppService.ExecuteSaveAsync( EntityConstant.OBJECTCRMSALESLEADSCORINGCONFIG, isNew ? EventTypeConstant.SAVECRMSALESLEADSCORINGCONFIGEVENTTYPEID : EventTypeConstant.UPDATECRMSALESLEADSCORINGCONFIGEVENTTYPEID, leadScoringConfigDTO, loginDTO, async tx => { _ = isNew ? await _LeadScoringConfigDAL.SaveLeadScoringConfig(leadScoringConfigDTO, loginDTO, tx, ct).ConfigureAwait(false) : await _LeadScoringConfigDAL.UpdateLeadScoringConfig(leadScoringConfigDTO, loginDTO, tx, ct).ConfigureAwait(false); return leadScoringConfigDTO.LeadScoringConfigId; }, null, -1, -1, null, isNew).ConfigureAwait(false); GB5Trace.Step("event-publish", new { EventTypeId = isNew ? EventTypeConstant.SAVECRMSALESLEADSCORINGCONFIGEVENTTYPEID : EventTypeConstant.UPDATECRMSALESLEADSCORINGCONFIGEVENTTYPEID }); var cacheKey = new CacheKeyGeneration().KeyGeneration( leadScoringConfigDTO.LeadScoringConfigId, EntityConstant.OBJECTCRMSALESLEADSCORINGCONFIG, CacheKeyLevel.CLIENT_LEVEL, loginDTO); await _KeyInvalidate.AllInvalidateCache(cacheKey).ConfigureAwait(false); _Logger.LogInformation("LeadScoringConfig {LeadScoringConfigId} {Action} by user {UserId}", leadScoringConfigDTO.LeadScoringConfigId, isNew ? "saved" : "updated", loginDTO.UserId); return isNew ? $"{SuccessResponse.SaveSuccessMessage} {leadScoringConfigDTO.LeadScoringConfigId}" : $"{SuccessResponse.UpdateSuccessMessage} {leadScoringConfigDTO.LeadScoringConfigId}"; } catch (ValidationException vex) { GB5Trace.MarkFailed("save-leadscoringconfig-failed", vex); if (autoNumberDTO != null && leadScoringConfigDTO.LeadScoringConfigId > 0) await _AutoNumber.RollbackAutoNumber("LEADSCORINGCONFIG", leadScoringConfigDTO.LeadScoringConfigId, loginDTO).ConfigureAwait(false); throw new Exception(vex.Message); } catch (Exception ex) { GB5Trace.MarkFailed("save-leadscoringconfig-failed", ex); _Logger.LogError(ex, "SaveLeadScoringConfig failed for LeadScoringConfigId {LeadScoringConfigId}", leadScoringConfigDTO.LeadScoringConfigId); if (autoNumberDTO != null && leadScoringConfigDTO.LeadScoringConfigId > 0) await _AutoNumber.RollbackAutoNumber("LEADSCORINGCONFIG", leadScoringConfigDTO.LeadScoringConfigId, loginDTO).ConfigureAwait(false); throw; } } public async Task GetSelectListLeadScoringConfig(CriteriaDTO criteriaDTO, LoginDTO loginDTO, CancellationToken ct) { try { return await _LeadScoringConfigDAL.GetSelectListLeadScoringConfig(criteriaDTO, loginDTO, ct).ConfigureAwait(false); } catch (Exception ex) { GB5Trace.MarkFailed("get-selectlist-leadscoringconfig-failed", ex); _Logger.LogError(ex, "GetSelectListLeadScoringConfig failed"); throw; } } public async Task DeleteLeadScoringConfig(int leadScoringConfigId, LoginDTO loginDTO, CancellationToken ct) { try { GB5Trace.Step("delete-leadscoringconfig", new { leadScoringConfigId }); var result = await _LeadScoringConfigDAL.DeleteLeadScoringConfig(leadScoringConfigId, loginDTO, ct).ConfigureAwait(false); var cacheKey = new CacheKeyGeneration().KeyGeneration( leadScoringConfigId, EntityConstant.OBJECTCRMSALESLEADSCORINGCONFIG, CacheKeyLevel.CLIENT_LEVEL, loginDTO); await _KeyInvalidate.AllInvalidateCache(cacheKey).ConfigureAwait(false); _Logger.LogInformation("LeadScoringConfig {LeadScoringConfigId} deleted by user {UserId}", leadScoringConfigId, loginDTO.UserId); return result; } catch (Exception ex) { GB5Trace.MarkFailed("delete-leadscoringconfig-failed", ex); _Logger.LogError(ex, "DeleteLeadScoringConfig failed for LeadScoringConfigId {LeadScoringConfigId}", leadScoringConfigId); throw; } } }