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;
}
}
}