using GB5Shared.DTO.Framework.Login; using RecruitmentDAL.DTO.Application; using RecruitmentDAL.DTO.Interview; using RecruitmentDAL.DTO.JobOffer; using RecruitmentDAL.DTO.JobRequisition; namespace RecruitmentBLL.Integration { /// Outcome of a call that creates/mutates something in ECP — modeled after /// DXPBLL.VendorPo.MmIntegrationService's Outcome pattern. Never throws out to the caller; /// a failed ECP call degrades to Success=false with an Error, so an ECP outage never breaks /// an Interview/Application save (see EcpIntegrationService's class doc comment). public class EcpCallOutcome { public bool Success { get; set; } public string? Error { get; set; } } /// Outcome of ScheduleMeetingAsync — carries the ECP-side MeetingId back so the /// caller can persist it onto TINTERVIEW.ECPMEETINGID. public class EcpMeetingScheduleOutcome : EcpCallOutcome { public int MeetingId { get; set; } = -1; } /// /// Cross-host HTTP integration with ECP (hosted by EngagementHost, a different process than /// Recruitment's HRFinanceHost) — no ProjectReference/DI injection of ECP's BLL exists or is /// possible across hosts (confirmed: no example anywhere in this repo of one module directly /// injecting another host's BLL), so every call here is a named-HttpClient POST/GET against /// ECP's verified FastEndpoints routes, following the exact /// IHttpClientFactory + "Login" header pattern used by /// DXPBLL.VendorPo.MmIntegrationService / PAYBLL.Integration.FinanceIntegrationService. /// /// Every method here degrades gracefully: a non-2xx response or thrown exception (timeout, /// DNS failure, ECP process down) is logged and returned as a failure/null result — it never /// throws back into InterviewBLL/ApplicationBLL, so an ECP outage never blocks an /// Interview/Application save. /// public interface IEcpIntegrationService { /// POSTs /Meeting/SaveMeeting for the interview, then POSTs /// /MeetingDetails/AddMeetingParticipant for the lead InterviewerId only — panel-member /// fan-out (TINTERVIEWPANEL) is a fast-follow once that entity's schema/BLL lands from a /// parallel task, not implemented here. Task ScheduleMeetingAsync(InterviewDTO interview, LoginDTO login, CancellationToken ct); /// POSTs /MeetingRecording/AttachRecording — the caller must have already /// uploaded the recording as a TATTACHMENT and pass its id. Task AttachInterviewRecordingAsync(int interviewId, int recordingAttachmentId, LoginDTO login, CancellationToken ct); /// GETs /MeetingRecording/GetRecordingTranscriptsForObject. Returns the raw JSON /// array (ECP's ResponseStandardDTO.Body content) so Recruitment never needs a /// ProjectReference to ECPDAL's RecordingTranscriptDTO; null on failure. Task GetInterviewRecordingsAsync(int interviewId, LoginDTO login, CancellationToken ct); /// POSTs /CommentThread/SaveCommentThread against the Application object. Task PostApplicationCommentAsync(int applicationId, string commentText, List mentionedUserIds, LoginDTO login, CancellationToken ct); /// GETs /CommentThread/GetCommentThreadTree for the Application object. Returns /// the raw JSON tree (ECP's ResponseStandardDTO.Body content); null on failure. Task GetApplicationCommentsAsync(int applicationId, LoginDTO login, CancellationToken ct); // ── Phase 3: Correspondence (offer/interview/rejection letters) ──────────────────── // All four POST /Correspondence/SaveAndSendCorrespondence with the matching // RecruitmentCorrespondenceTypes.* CorrespondenceTypeId and a TemplateId read from // "Integration:CorrespondenceTemplates:*" config (an ECP TATTACHMENT id of a previously // uploaded .docx/.xlsx template — see SaveAndSendCorrespondence's real request shape). // If that TemplateId isn't configured (<=0), or the recipient email is empty, the call // degrades to Success=false without an HTTP round-trip — same graceful-degradation // contract as every other method on this interface, extended to cover "not configured yet" // as well as "ECP unreachable". /// Sends the offer letter for a newly-saved JobOffer. Merge fields are built from /// JobOfferDTO's own columns (CTC, ExpectedJoiningDate, OfferExpiryDate, JobPositionId as /// Position) — no cross-module JobPosition-title lookup is performed. Task SendOfferLetterAsync(JobOfferDTO jobOffer, string recipientEmail, LoginDTO login, CancellationToken ct); /// Sends an interview scheduling notification for a newly-saved or rescheduled /// Interview. Task SendInterviewInviteAsync(InterviewDTO interview, string recipientEmail, LoginDTO login, CancellationToken ct); /// Sends a candidate rejection letter for an Application. Task SendRejectionLetterAsync(ApplicationDTO application, string recipientEmail, LoginDTO login, CancellationToken ct); /// Notifies the original JobRequisition requester (RequestedById, resolved to an /// email via MUSER.PRIMARYMAIL — same column CRMDAL/ECPDAL's own MUSER lookups already use) /// that their hire has joined — closes the loop back to whoever asked for the position. /// Degrades gracefully (Success=false) if RequestedById has no resolvable email. Task NotifyRequesterOnHireAsync(JobRequisitionDTO jobRequisition, string hiredCandidateName, LoginDTO login, CancellationToken ct); } }