using System.ComponentModel.DataAnnotations; using System.Linq; 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 MarketingBLL.BusinessAspirationGoal; using MarketingDAL.CustomCode.CustomerProductMatrixCell; using MarketingDAL.DTO.CustomerProductMatrixCell; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using static GB5Shared.GB5Constant.Constant; namespace MarketingBLL.CustomerProductMatrixCell; /// /// BLL for MCUSTPRODMATRIXCELL — the "one page business model" synthesis table. ABCDCategory/ /// ProductLabel are validated against ICPBLL.ComputeABCDCategory's / ProductBLL.ComputeProductLabel's /// exact derived string sets (see the const arrays below) rather than re-declared as byte enums, /// per this task's brief. RevenueTargetAmount is computed on every GetCustomerProductMatrixCell read /// from the tenant's current BusinessAspirationGoal — never stored, never computed on Save/Update. /// public class CustomerProductMatrixCellBLL : ICustomerProductMatrixCellBLL { /// Must match ICPBLL.ComputeABCDCategory's exact output strings. private static readonly string[] ValidABCDCategories = { "Amazing", "Breadwinning", "Convenience", "Dangerous" }; /// Must match ProductBLL.ComputeProductLabel's exact output strings. private static readonly string[] ValidProductLabels = { "Easy-to-Buy", "Primary", "High-Value", "Time-Waster" }; private readonly ICustomerProductMatrixCellDAL _CustomerProductMatrixCellDAL; private readonly IBusinessAspirationGoalBLL _BusinessAspirationGoalBLL; private readonly AutoNumber _AutoNumber; private readonly IValidation _Validation; private readonly KeyInvalidate _KeyInvalidate; private readonly BaseEntityAppService _BaseEntityAppService; private readonly ILogger _Logger; public CustomerProductMatrixCellBLL( ICustomerProductMatrixCellDAL customerProductMatrixCellDAL, IBusinessAspirationGoalBLL businessAspirationGoalBLL, AutoNumber autoNumber, IValidation validation, KeyInvalidate keyInvalidate, BaseEntityAppService baseEntityAppService, ILogger logger) { _CustomerProductMatrixCellDAL = customerProductMatrixCellDAL; _BusinessAspirationGoalBLL = businessAspirationGoalBLL; _AutoNumber = autoNumber; _Validation = validation; _KeyInvalidate = keyInvalidate; _BaseEntityAppService = baseEntityAppService; _Logger = logger; } public async Task GetCustomerProductMatrixCell(int customerProductMatrixCellId, LoginDTO loginDTO, CancellationToken ct) { try { var dto = await _CustomerProductMatrixCellDAL.GetCustomerProductMatrixCell(customerProductMatrixCellId, loginDTO, ct).ConfigureAwait(false); if (dto != null) { // Compute (never store) RevenueTargetAmount from the tenant's current // BusinessAspirationGoal. Null (not a throw) when none exists yet. var goal = await _BusinessAspirationGoalBLL.GetLatestBusinessAspirationGoal(loginDTO, ct).ConfigureAwait(false); dto.RevenueTargetAmount = goal != null ? dto.RevenueTargetPercent / 100m * goal.AnnualRevenueTarget : null; } return JsonConvert.SerializeObject(dto); } catch (Exception ex) { GB5Trace.MarkFailed("get-customerproductmatrixcell-failed", ex); _Logger.LogError(ex, "GetCustomerProductMatrixCell failed for CustomerProductMatrixCellId {CustomerProductMatrixCellId}", customerProductMatrixCellId); throw; } } public async Task SaveCustomerProductMatrixCell(CustomerProductMatrixCellDTO customerProductMatrixCellDTO, LoginDTO loginDTO, CancellationToken ct) { if (customerProductMatrixCellDTO == null) throw new ArgumentNullException(nameof(customerProductMatrixCellDTO)); AutoNumberDTO? autoNumberDTO = null; try { GB5Trace.Step("validate-customerproductmatrixcell", new { customerProductMatrixCellDTO.CustomerProductMatrixCellId }); await _Validation.NotEmpty(customerProductMatrixCellDTO.CellCode, nameof(customerProductMatrixCellDTO.CellCode)); await _Validation.NotEmpty(customerProductMatrixCellDTO.CellName, nameof(customerProductMatrixCellDTO.CellName)); await _Validation.NotEmpty(customerProductMatrixCellDTO.ABCDCategory, nameof(customerProductMatrixCellDTO.ABCDCategory)); await _Validation.NotEmpty(customerProductMatrixCellDTO.ProductLabel, nameof(customerProductMatrixCellDTO.ProductLabel)); if (!ValidABCDCategories.Contains(customerProductMatrixCellDTO.ABCDCategory)) throw new ValidationException( $"ABCDCategory must be one of: {string.Join(", ", ValidABCDCategories)} (got '{customerProductMatrixCellDTO.ABCDCategory}')."); if (!ValidProductLabels.Contains(customerProductMatrixCellDTO.ProductLabel)) throw new ValidationException( $"ProductLabel must be one of: {string.Join(", ", ValidProductLabels)} (got '{customerProductMatrixCellDTO.ProductLabel}')."); // RevenueTargetAmount is computed-on-read only — never persisted. customerProductMatrixCellDTO.RevenueTargetAmount = null; customerProductMatrixCellDTO.ModifiedById = loginDTO.UserId; customerProductMatrixCellDTO.ModifiedOn = DateTime.UtcNow; bool isNew = customerProductMatrixCellDTO.CustomerProductMatrixCellId == 0; if (isNew) { autoNumberDTO = await _AutoNumber.GetNumberAsync(1, "CUSTPRODMATRIXCELL", loginDTO).ConfigureAwait(false); customerProductMatrixCellDTO.CustomerProductMatrixCellId = autoNumberDTO.StartNumber; customerProductMatrixCellDTO.CreatedById = loginDTO.UserId; customerProductMatrixCellDTO.CreatedOn = DateTime.UtcNow; } GB5Trace.Step("save-customerproductmatrixcell", new { customerProductMatrixCellDTO.CustomerProductMatrixCellId, isNew }); await _BaseEntityAppService.ExecuteSaveAsync( EntityConstant.OBJECTMARKETINGCUSTPRODMATRIXCELL, isNew ? EventTypeConstant.SAVEMARKETINGCUSTPRODMATRIXCELLEVENTTYPEID : EventTypeConstant.UPDATEMARKETINGCUSTPRODMATRIXCELLEVENTTYPEID, customerProductMatrixCellDTO, loginDTO, async tx => { _ = isNew ? await _CustomerProductMatrixCellDAL.SaveCustomerProductMatrixCell(customerProductMatrixCellDTO, loginDTO, tx, ct).ConfigureAwait(false) : await _CustomerProductMatrixCellDAL.UpdateCustomerProductMatrixCell(customerProductMatrixCellDTO, loginDTO, tx, ct).ConfigureAwait(false); return customerProductMatrixCellDTO.CustomerProductMatrixCellId; }, null, -1, -1, null, isNew).ConfigureAwait(false); GB5Trace.Step("event-publish", new { EventTypeId = isNew ? EventTypeConstant.SAVEMARKETINGCUSTPRODMATRIXCELLEVENTTYPEID : EventTypeConstant.UPDATEMARKETINGCUSTPRODMATRIXCELLEVENTTYPEID }); var cacheKey = new CacheKeyGeneration().KeyGeneration( customerProductMatrixCellDTO.CustomerProductMatrixCellId, EntityConstant.OBJECTMARKETINGCUSTPRODMATRIXCELL, CacheKeyLevel.CLIENT_LEVEL, loginDTO); await _KeyInvalidate.AllInvalidateCache(cacheKey).ConfigureAwait(false); _Logger.LogInformation("CustomerProductMatrixCell {CustomerProductMatrixCellId} {Action} by user {UserId}", customerProductMatrixCellDTO.CustomerProductMatrixCellId, isNew ? "saved" : "updated", loginDTO.UserId); return isNew ? $"{SuccessResponse.SaveSuccessMessage} {customerProductMatrixCellDTO.CustomerProductMatrixCellId}" : $"{SuccessResponse.UpdateSuccessMessage} {customerProductMatrixCellDTO.CustomerProductMatrixCellId}"; } catch (ValidationException vex) { GB5Trace.MarkFailed("save-customerproductmatrixcell-failed", vex); if (autoNumberDTO != null && customerProductMatrixCellDTO.CustomerProductMatrixCellId > 0) await _AutoNumber.RollbackAutoNumber("CUSTPRODMATRIXCELL", customerProductMatrixCellDTO.CustomerProductMatrixCellId, loginDTO).ConfigureAwait(false); throw new Exception(vex.Message); } catch (Exception ex) { GB5Trace.MarkFailed("save-customerproductmatrixcell-failed", ex); _Logger.LogError(ex, "SaveCustomerProductMatrixCell failed for CustomerProductMatrixCellId {CustomerProductMatrixCellId}", customerProductMatrixCellDTO.CustomerProductMatrixCellId); if (autoNumberDTO != null && customerProductMatrixCellDTO.CustomerProductMatrixCellId > 0) await _AutoNumber.RollbackAutoNumber("CUSTPRODMATRIXCELL", customerProductMatrixCellDTO.CustomerProductMatrixCellId, loginDTO).ConfigureAwait(false); throw; } } public async Task GetSelectListCustomerProductMatrixCell(CriteriaDTO criteriaDTO, LoginDTO loginDTO, CancellationToken ct) { try { return await _CustomerProductMatrixCellDAL.GetSelectListCustomerProductMatrixCell(criteriaDTO, loginDTO, ct).ConfigureAwait(false); } catch (Exception ex) { GB5Trace.MarkFailed("get-selectlist-customerproductmatrixcell-failed", ex); _Logger.LogError(ex, "GetSelectListCustomerProductMatrixCell failed"); throw; } } public async Task DeleteCustomerProductMatrixCell(int customerProductMatrixCellId, LoginDTO loginDTO, CancellationToken ct) { try { GB5Trace.Step("delete-customerproductmatrixcell", new { customerProductMatrixCellId }); var result = await _CustomerProductMatrixCellDAL.DeleteCustomerProductMatrixCell(customerProductMatrixCellId, loginDTO, ct).ConfigureAwait(false); var cacheKey = new CacheKeyGeneration().KeyGeneration( customerProductMatrixCellId, EntityConstant.OBJECTMARKETINGCUSTPRODMATRIXCELL, CacheKeyLevel.CLIENT_LEVEL, loginDTO); await _KeyInvalidate.AllInvalidateCache(cacheKey).ConfigureAwait(false); _Logger.LogInformation("CustomerProductMatrixCell {CustomerProductMatrixCellId} deleted by user {UserId}", customerProductMatrixCellId, loginDTO.UserId); return result; } catch (Exception ex) { GB5Trace.MarkFailed("delete-customerproductmatrixcell-failed", ex); _Logger.LogError(ex, "DeleteCustomerProductMatrixCell failed for CustomerProductMatrixCellId {CustomerProductMatrixCellId}", customerProductMatrixCellId); throw; } } }