using System.Linq; using EntitlementDAL.DTOs; using EntitlementDAL.Interfaces; using GB5Shared.DTO.Framework.Login; using GB5Shared.GenerateAutoNumber; using static GB5Shared.GB5Constant.Constant; namespace EntitlementBLL.Legal; /// Phase 0's interim implementation of IAgreementConsentProvider — backed by /// Entitlement's own MAGREEMENTTYPE/MAGREEMENTVERSION/LAGREEMENTACCEPTANCE tables (tracker /// §50/§51). Swapped for a real ClmAgreementConsentClient once the full "GoodBooks CLM" product /// ships its own future session — every call site using IAgreementConsentProvider needs no /// change when that swap happens. public class LocalAgreementConsentProvider : IAgreementConsentProvider { private readonly IAgreementDAL _AgreementDAL; private readonly AutoNumber _AutoNumber; public LocalAgreementConsentProvider(IAgreementDAL agreementDAL, AutoNumber autoNumber) { _AgreementDAL = agreementDAL; _AutoNumber = autoNumber; } public async Task GetApplicableAgreementsAsync( string jurisdictionCode, string[] agreementTypeCodes, LoginDTO login, CancellationToken ct) { if (agreementTypeCodes is null || agreementTypeCodes.Length == 0) throw new ArgumentException("At least one AgreementTypeCode is required.", nameof(agreementTypeCodes)); var versions = await _AgreementDAL.GetApplicableVersionsAsync(agreementTypeCodes, jurisdictionCode ?? string.Empty, login, ct) .ConfigureAwait(false); return new AgreementBundleDTO { Versions = versions }; } public async Task GetPendingIndividualAgreementVersionIdsAsync( AgreementSubjectType subjectType, int subjectId, string jurisdictionCode, LoginDTO login, CancellationToken ct) { var required = await _AgreementDAL.GetRequiredIndividualVersionsAsync(jurisdictionCode ?? string.Empty, login, ct).ConfigureAwait(false); if (required.Count == 0) return Array.Empty(); var accepted = await _AgreementDAL.GetAcceptancesBySubjectAsync((byte)subjectType, subjectId, login, ct).ConfigureAwait(false); var acceptedVersionIds = accepted.Select(a => a.AgreementVersionId).ToHashSet(); return required .Where(v => !acceptedVersionIds.Contains(v.AgreementVersionId)) .Select(v => v.AgreementVersionId) .ToArray(); } public async Task RecordAcceptanceAsync( AgreementSubjectType subjectType, int subjectId, int[] agreementVersionIds, string? ipAddress, string? userAgent, AgreementAcceptanceMethod method, string correlationKey, LoginDTO login, CancellationToken ct) { if (agreementVersionIds is null || agreementVersionIds.Length == 0) throw new ArgumentException("At least one AgreementVersionId is required.", nameof(agreementVersionIds)); if (string.IsNullOrWhiteSpace(correlationKey)) throw new ArgumentException("CorrelationKey is required.", nameof(correlationKey)); var now = DateTime.UtcNow; // Re-validate each version is still Published before writing — a version can go stale // between page-load and submit (the exact same re-validation discipline this engagement // already applies to a rollout/change-request's own eligibility checks, e.g. §40's Gate // A/B re-check at delivery time, not just at target-resolution time). foreach (var agreementVersionId in agreementVersionIds) { var version = await _AgreementDAL.GetVersionByIdAsync(agreementVersionId, login, ct).ConfigureAwait(false) ?? throw new InvalidOperationException($"AgreementVersionId {agreementVersionId} not found."); if (version.VersionStatus != (byte)AgreementVersionStatus.Published) throw new InvalidOperationException( $"AgreementVersionId {agreementVersionId} is no longer Published (current status: {(AgreementVersionStatus)version.VersionStatus}) — refresh the bundle and ask the subject to re-accept."); } foreach (var agreementVersionId in agreementVersionIds) { var auto = await _AutoNumber.GetNumberAsync(1, AUTONUMBERCONSTANT.ENTITLEMENTAGREEMENTACCEPTANCE, login).ConfigureAwait(false); var dto = new AgreementAcceptanceDTO { AgreementAcceptanceId = auto.StartNumber, SubjectType = (byte)subjectType, SubjectId = subjectId, AgreementVersionId = agreementVersionId, AcceptedOn = now, IpAddress = ipAddress, UserAgent = userAgent, AcceptanceMethod = (byte)method, CorrelationKey = correlationKey, CreatedById = login.UserId, CreatedOn = now, TenantId = login.ClientId, }; await _AgreementDAL.InsertAcceptanceAsync(dto, login, ct).ConfigureAwait(false); } } }