using GB5Shared.DTO.Framework.Login; namespace RecruitmentBLL.Integration { /// Outcome of a call that mutates something in OKR — same {Success, Error} shape as /// EcpCallOutcome, kept as its own type so OKR integration stays self-contained and doesn't /// couple to ECP's naming. Never throws out to the caller; a failed OKR call degrades to /// Success=false with an Error, so an OKR outage (or a missing Goal) never breaks the /// Recruitment save/transition that triggered the push. public class OkrCallOutcome { public bool Success { get; set; } public string? Error { get; set; } } /// A Goal found for a recruiter, carrying just enough of TGOAL's denormalized state /// (CurrentValue/CurrentProgress/TargetValue) for the caller to compute an incremented value /// and a new progress percentage before pushing it back via PushGoalProgressAsync. public class OkrGoalLookupResult { public int GoalId { get; set; } public string Name { get; set; } = string.Empty; public string KRAName { get; set; } = string.Empty; public decimal CurrentValue { get; set; } public decimal CurrentProgress { get; set; } public decimal TargetValue { get; set; } } /// /// Cross-host HTTP integration with OKR (hosted by EngagementHost, a different process than /// Recruitment's HRFinanceHost — no ProjectReference/DI injection of OKR's BLL is possible /// across hosts), following the exact IHttpClientFactory + "Login" header pattern used by /// IEcpIntegrationService/EcpIntegrationService — read that class first, it is the template /// for this one. /// /// Feeds recruitment team metrics (time-to-fill, offer-acceptance, requisitions closed) into /// OKR's existing PERM/OKR KRA-tracking mechanism (Phase 5 of the Recruitment build-out plan) /// instead of a recruitment-only scorecard — see GoalBLL.UpdateGoalProgressAsync (the real, /// generic, programmatic progress-update mechanism) and its OKRSL wrapper endpoint /// POST /Goal/UpdateGoalProgress. /// /// A Goal row must already exist (linking a recruiter Employee to a KRA for an active /// OKRCycle) before a push can target it — Recruitment cannot create Goals itself, that is /// PERM/OKR admin setup, out of scope here. Most recruiters will NOT have a /// recruitment-specific Goal provisioned yet; FindGoalForRecruiterAsync returning null is the /// expected, common case, not a failure — callers must log at Info and continue, never treat /// it as a warning. /// /// Every method here degrades gracefully: a non-2xx response, an unreachable OKR process, or /// any thrown exception is logged and returned as a failure/null result — it never throws /// back into JobRequisitionBLL/JobOfferBLL/CandidateConversionBLL, so an OKR outage never /// blocks the underlying Recruitment action that triggered the push. /// /// NOTE for whoever next touches this: as of this writing OKRSL is NOT wired into /// EngagementHost's ProjectReferences (see EngagementHost.csproj's own comment: "OKRSL /// deferred: GoalBLL depends on concrete ObjectiveBLL, not IObjectiveBLL; DI resolution fails /// at startup") — so in the current multi-host deployment topology, calls through this /// service will fail with a connection error until either that DI bug is fixed and OKRSL is /// added back to EngagementHost, or OKRSL is run standalone on its own configured port. This /// is exactly the "OKR outage" case the graceful-degradation contract above exists for; no /// code here assumes OKR is reachable. /// public interface IOkrIntegrationService { /// Looks up an existing Goal for the given recruiter (TGOAL.EMPLOYEEID — /// same "EmployeeId is actually populated with UserId" convention GoalBLL.GetMyGoalsAsync /// itself relies on for ESS) that loosely matches against /// the Goal's KRAName or Name (case-insensitive substring match). /// /// There is no single "goals for employee across all cycles" endpoint — GetGoalList /// requires a real OKRCycleId — so this first resolves every currently-Active OKRCycle /// (GET /Cycle/GetOKRCycleList?Status=1) and searches each one's goal list /// (GET /Goal/GetGoalList?OKRCycleId=..&EmployeeId=..) for a match. /// /// Returns null — not an exception — when OKR is unreachable, when the recruiter has no /// Active-cycle goals at all, or when none of their goals match the hint. All three are /// expected, normal outcomes for most recruiters; log at Info, not Warning. Task FindGoalForRecruiterAsync( int recruiterUserId, string kraNameHint, LoginDTO login, CancellationToken ct); /// Thin wrapper over POST /Goal/UpdateGoalProgress (OKRSL.Endpoints.Goal /// .UpdateGoalProgress) — that endpoint binds every field as a query parameter, not a /// [FromBody] payload (see its Params record), so this posts a query string with an empty /// body. ConfidenceLevel is left at the endpoint's own default (1). Never throws; a /// non-2xx response or transport failure returns Success=false with a logged /// warning. Task PushGoalProgressAsync( int goalId, decimal currentValue, decimal progressPercent, string remarks, LoginDTO login, CancellationToken ct); } }