using GB5Shared.DaprCache; using GB5Shared.DTO.Framework.Login; using GB5Shared.EventLogPublish; using GB5Shared.Resource.Response; using GB5Shared.Telemetry; using GB5Shared.Validation; using Microsoft.Extensions.Logging; using OKRBLL.Interfaces; using OKRDAL.DTOs; using OKRDAL.Interfaces; using System.Text.Json; using static GB5Shared.GB5Constant.Constant; namespace OKRBLL.Implementations; public class ObjectiveBLL : IObjectiveBLL { private readonly IObjectiveDAL _dal; private readonly IValidation _validation; private readonly KeyInvalidate _keyInvalidate; private readonly EventLogPublish _eventLog; private readonly ILogger _logger; public ObjectiveBLL( IObjectiveDAL dal, IValidation validation, KeyInvalidate keyInvalidate, EventLogPublish eventLog, ILogger logger) { _dal = dal; _validation = validation; _keyInvalidate = keyInvalidate; _eventLog = eventLog; _logger = logger; } public async Task GetObjectiveAsync(int objectiveId, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("get-objective", new { objectiveId }); var dto = await _dal.GetObjectiveAsync(objectiveId, login, ct).ConfigureAwait(false); return JsonSerializer.Serialize(dto); } catch (Exception ex) { GB5Trace.MarkFailed("get-objective-failed", ex); _logger.LogError(ex, "GetObjective failed for ObjectiveId {ObjectiveId}", objectiveId); throw; } } public async Task GetObjectiveListAsync(int okrCycleId, int objectiveType, int ownerId, int forLevel, int forObjectId, int page, int pageSize, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("get-objective-list", new { okrCycleId, objectiveType }); var list = await _dal.GetObjectiveListAsync(okrCycleId, objectiveType, ownerId, forLevel, forObjectId, page, pageSize, login, ct).ConfigureAwait(false); return JsonSerializer.Serialize(list); } catch (Exception ex) { GB5Trace.MarkFailed("get-objective-list-failed", ex); _logger.LogError(ex, "GetObjectiveList failed for OKRCycleId {OKRCycleId}", okrCycleId); throw; } } public async Task GetObjectiveTreeAsync(int okrCycleId, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("get-objective-tree", new { okrCycleId }); var tree = await _dal.GetObjectiveTreeAsync(okrCycleId, login, ct).ConfigureAwait(false); return JsonSerializer.Serialize(tree); } catch (Exception ex) { GB5Trace.MarkFailed("get-objective-tree-failed", ex); _logger.LogError(ex, "GetObjectiveTree failed for OKRCycleId {OKRCycleId}", okrCycleId); throw; } } public async Task GetObjectiveAlignmentAsync(int okrCycleId, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("get-objective-alignment", new { okrCycleId }); var alignment = await _dal.GetAlignmentViewAsync(okrCycleId, login, ct).ConfigureAwait(false); return JsonSerializer.Serialize(alignment); } catch (Exception ex) { GB5Trace.MarkFailed("get-objective-alignment-failed", ex); _logger.LogError(ex, "GetObjectiveAlignment failed for OKRCycleId {OKRCycleId}", okrCycleId); throw; } } public async Task SaveObjectiveAsync(ObjectiveDTO dto, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("validate-objective", new { dto.ObjectiveId }); await _validation.NotEmpty(dto.ObjectiveName, nameof(dto.ObjectiveName)).ConfigureAwait(false); bool isNew = dto.ObjectiveId == 0; GB5Trace.Step("save-objective", new { dto.ObjectiveId, isNew }); int objectiveId = await _dal.SaveObjectiveAsync(dto, login, ct).ConfigureAwait(false); GB5Trace.Step("event-publish", new { EventTypeId = EventTypeConstant.SAVEOKROBJECTIVEEVENTTYPEID }); await _eventLog.PublishEventLogAsync( isNew ? "Objective Created" : "Objective Updated", new { dto.ObjectiveName, dto.ObjectiveType }, EventTypeConstant.SAVEOKROBJECTIVEEVENTTYPEID, objectiveId, login, ct: ct).ConfigureAwait(false); var cacheKey = new CacheKeyGeneration().KeyGeneration(objectiveId, EntityConstant.OBJECTOBJECTIVE, CacheKeyLevel.CLIENT_LEVEL, login); await _keyInvalidate.AllInvalidateCache(cacheKey).ConfigureAwait(false); return isNew ? $"{SuccessResponse.SaveSuccessMessage} {objectiveId}" : SuccessResponse.UpdateSuccess; } catch (Exception ex) { GB5Trace.MarkFailed("save-objective-failed", ex); _logger.LogError(ex, "SaveObjective failed for Name {Name}", dto.ObjectiveName); throw; } } public async Task DeleteObjectiveAsync(int objectiveId, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("delete-objective", new { objectiveId }); await _dal.DeleteObjectiveAsync(objectiveId, login, ct).ConfigureAwait(false); var cacheKey = new CacheKeyGeneration().KeyGeneration(objectiveId, EntityConstant.OBJECTOBJECTIVE, CacheKeyLevel.CLIENT_LEVEL, login); await _keyInvalidate.AllInvalidateCache(cacheKey).ConfigureAwait(false); return SuccessResponse.DeleteSuccessMessage; } catch (Exception ex) { GB5Trace.MarkFailed("delete-objective-failed", ex); _logger.LogError(ex, "DeleteObjective failed for ObjectiveId {ObjectiveId}", objectiveId); throw; } } public async Task SaveAlignmentAsync(OKRAlignmentDTO dto, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("save-alignment", new { dto.SourceObjectiveId, dto.TargetObjectiveId }); if (dto.SourceObjectiveId == dto.TargetObjectiveId) return "Source and target objectives must be different."; int alignmentId = await _dal.SaveAlignmentAsync(dto, login, ct).ConfigureAwait(false); return $"{SuccessResponse.SaveSuccessMessage} {alignmentId}"; } catch (Exception ex) { GB5Trace.MarkFailed("save-alignment-failed", ex); _logger.LogError(ex, "SaveAlignment failed for SourceObjectiveId {SourceId}", dto.SourceObjectiveId); throw; } } public async Task DeleteAlignmentAsync(int alignmentId, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("delete-alignment", new { alignmentId }); await _dal.DeleteAlignmentAsync(alignmentId, login, ct).ConfigureAwait(false); return SuccessResponse.DeleteSuccessMessage; } catch (Exception ex) { GB5Trace.MarkFailed("delete-alignment-failed", ex); _logger.LogError(ex, "DeleteAlignment failed for AlignmentId {AlignmentId}", alignmentId); throw; } } // Called by GoalBLL (via IObjectiveBLL) after every progress update — not exposed as endpoint public async Task UpdateObjectiveOverallProgressAsync(int objectiveId, LoginDTO login, CancellationToken ct) { try { var progress = await _dal.GetWeightedProgressAsync(objectiveId, login, ct).ConfigureAwait(false); short status = progress switch { 0 => 0, // NotStarted >= 100 => 4, // Achieved >= 75 => 1, // OnTrack >= 50 => 3, // NeedsWork _ => 1 // OnTrack }; await _dal.UpdateOverallProgressAsync(objectiveId, progress, status, login, ct).ConfigureAwait(false); } catch (Exception ex) { GB5Trace.MarkFailed("update-objective-progress-failed", ex); _logger.LogError(ex, "UpdateObjectiveOverallProgress failed for ObjectiveId {ObjectiveId}", objectiveId); throw; } } }