diff --git a/src/api/src/application/Yoma.Core.Api/Controllers/MyOpportunityController.cs b/src/api/src/application/Yoma.Core.Api/Controllers/MyOpportunityController.cs index 975a02f14..4baecb69e 100644 --- a/src/api/src/application/Yoma.Core.Api/Controllers/MyOpportunityController.cs +++ b/src/api/src/application/Yoma.Core.Api/Controllers/MyOpportunityController.cs @@ -283,13 +283,13 @@ public async Task PerformActionSendForVerificationManual([FromRou [HttpPut("action/link/{linkId}/verify")] [ProducesResponseType((int)HttpStatusCode.OK)] [Authorize(Roles = $"{Constants.Role_User}")] - public async Task PerformActionInstantVerificationManual([FromRoute] Guid linkId) + public async Task PerformActionInstantVerification([FromRoute] Guid linkId) { - _logger.LogInformation("Handling request {requestName}", nameof(PerformActionInstantVerificationManual)); + _logger.LogInformation("Handling request {requestName}", nameof(PerformActionInstantVerification)); - await _myOpportunityService.PerformActionInstantVerificationManual(linkId); + await _myOpportunityService.PerformActionInstantVerification(linkId); - _logger.LogInformation("Request {requestName} handled", nameof(PerformActionInstantVerificationManual)); + _logger.LogInformation("Request {requestName} handled", nameof(PerformActionInstantVerification)); return StatusCode((int)HttpStatusCode.OK); } diff --git a/src/api/src/domain/Yoma.Core.Domain/ActionLink/Services/LinkService.cs b/src/api/src/domain/Yoma.Core.Domain/ActionLink/Services/LinkService.cs index 1a0224674..6f4769013 100644 --- a/src/api/src/domain/Yoma.Core.Domain/ActionLink/Services/LinkService.cs +++ b/src/api/src/domain/Yoma.Core.Domain/ActionLink/Services/LinkService.cs @@ -262,8 +262,11 @@ public async Task CreateVerify(LinkRequestCreateVerify request, bool e case LinkEntityType.Opportunity: var opportunity = ValidateAndInitializeOpportunity(request, item, false, ensureOrganizationAuthorization); - if (!opportunity.VerificationEnabled || opportunity.VerificationMethod != VerificationMethod.Manual) - throw new ValidationException($"Link cannot be created as the opportunity '{opportunity.Title}' does not support manual verification"); + if (!opportunity.VerificationEnabled) + throw new ValidationException($"Link cannot be created as the opportunity '{opportunity.Title}' does not support verification"); + + if (!opportunity.VerificationMethod.HasValue) //support any verification method + throw new DataInconsistencyException($"Data inconsistency detected: The opportunity '{opportunity.Title}' has verification enabled, but no verification method is set"); if (!opportunity.Published) throw new ValidationException($"Link cannot be created as the opportunity '{opportunity.Title}' has not been published"); @@ -341,8 +344,11 @@ public async Task UpdateStatus(Guid id, LinkRequestUpdateStatus reques var opportunity = _opportunityService.GetById(link.OpportunityId.Value, false, true, ensureOrganizationAuthorization); - if (!opportunity.VerificationEnabled || opportunity.VerificationMethod != VerificationMethod.Manual) - throw new ValidationException($"Link cannot be activated as the opportunity '{opportunity.Title}' does not support manual verification"); + if (!opportunity.VerificationEnabled) + throw new ValidationException($"Link cannot be activated as the opportunity '{opportunity.Title}' does not support verification"); + + if (!opportunity.VerificationMethod.HasValue) //support any verification method + throw new DataInconsistencyException($"Data inconsistency detected: The opportunity '{opportunity.Title}' has verification enabled, but no verification method is set"); if (!opportunity.Published) throw new ValidationException($"Link cannot be activated as the opportunity '{opportunity.Title}' has not been published"); diff --git a/src/api/src/domain/Yoma.Core.Domain/MyOpportunity/Interfaces/IMyOpportunityService.cs b/src/api/src/domain/Yoma.Core.Domain/MyOpportunity/Interfaces/IMyOpportunityService.cs index a04250c4b..acc8b18d2 100644 --- a/src/api/src/domain/Yoma.Core.Domain/MyOpportunity/Interfaces/IMyOpportunityService.cs +++ b/src/api/src/domain/Yoma.Core.Domain/MyOpportunity/Interfaces/IMyOpportunityService.cs @@ -50,6 +50,6 @@ public interface IMyOpportunityService Dictionary? ListAggregatedOpportunityByCompleted(bool includeExpired); - Task PerformActionInstantVerificationManual(Guid linkId); + Task PerformActionInstantVerification(Guid linkId); } } diff --git a/src/api/src/domain/Yoma.Core.Domain/MyOpportunity/Services/MyOpportunityService.cs b/src/api/src/domain/Yoma.Core.Domain/MyOpportunity/Services/MyOpportunityService.cs index fb0c24b9a..129b5c83f 100644 --- a/src/api/src/domain/Yoma.Core.Domain/MyOpportunity/Services/MyOpportunityService.cs +++ b/src/api/src/domain/Yoma.Core.Domain/MyOpportunity/Services/MyOpportunityService.cs @@ -698,17 +698,17 @@ public async Task PerformActionSendForVerificationManual(Guid userId, Guid oppor request.OverridePending = overridePending; - await PerformActionSendForVerificationManual(user, opportunityId, request); + await PerformActionSendForVerification(user, opportunityId, request, VerificationMethod.Manual); } public async Task PerformActionSendForVerificationManual(Guid opportunityId, MyOpportunityRequestVerify request) { var user = _userService.GetByUsername(HttpContextAccessorHelper.GetUsername(_httpContextAccessor, false), false, false); - await PerformActionSendForVerificationManual(user, opportunityId, request); + await PerformActionSendForVerification(user, opportunityId, request, VerificationMethod.Manual); } - public async Task PerformActionInstantVerificationManual(Guid linkId) + public async Task PerformActionInstantVerification(Guid linkId) { var link = _linkService.GetById(linkId, false, false); @@ -728,9 +728,9 @@ await _executionStrategyService.ExecuteInExecutionStrategyAsync(async () => await _linkService.LogUsage(link.Id); var request = new MyOpportunityRequestVerify { InstantVerification = true }; - await PerformActionSendForVerificationManual(user, link.EntityId, request); + await PerformActionSendForVerification(user, link.EntityId, request, null); //any verification method - await FinalizeVerificationManual(user, opportunity, VerificationStatus.Completed, true, "Auto-verification"); + await FinalizeVerification(user, opportunity, VerificationStatus.Completed, true, "Auto-verification"); scope.Complete(); }); @@ -820,7 +820,7 @@ public async Task FinalizeVerification user = _userService.GetById(item.UserId, false, false); opportunity = _opportunityService.GetById(item.OpportunityId, true, true, false); - await FinalizeVerificationManual(user, opportunity, request.Status, false, request.Comment); + await FinalizeVerification(user, opportunity, request.Status, false, request.Comment); var successItem = new MyOpportunityResponseVerifyFinalizeBatchItem { @@ -868,7 +868,7 @@ public async Task FinalizeVerificationManual(MyOpportunityRequestVerifyFinalize var user = _userService.GetById(request.UserId, false, false); var opportunity = _opportunityService.GetById(request.OpportunityId, true, true, false); - await FinalizeVerificationManual(user, opportunity, request.Status, false, request.Comment); + await FinalizeVerification(user, opportunity, request.Status, false, request.Comment); } public Dictionary? ListAggregatedOpportunityByViewed(bool includeExpired) @@ -953,7 +953,7 @@ public async Task FinalizeVerificationManual(MyOpportunityRequestVerifyFinalize } //supported statuses: Rejected or Completed - private async Task FinalizeVerificationManual(User user, Opportunity.Models.Opportunity opportunity, VerificationStatus status, bool instantVerification, string? comment) + private async Task FinalizeVerification(User user, Opportunity.Models.Opportunity opportunity, VerificationStatus status, bool instantVerification, string? comment) { //can complete, provided opportunity is published (and started) or expired (actioned prior to expiration) var canFinalize = opportunity.Status == Status.Expired; @@ -1038,7 +1038,6 @@ await _executionStrategyService.ExecuteInExecutionStrategyAsync(async () => await SendNotification(item, notificationType.Value); } - private void EnsureNoEarlierPendingVerificationsForOtherStudents(User user, Opportunity.Models.Opportunity opportunity, Models.MyOpportunity currentItem, bool instantVerification) { //ensure no pending verifications for other students who applied earlier @@ -1109,7 +1108,7 @@ private static string PerformActionNotPossibleValidationMessage(Opportunity.Mode return $"Opportunity '{opportunity.Title}' {description}, because {reasonText}. Please check these conditions and try again"; } - private async Task PerformActionSendForVerificationManual(User user, Guid opportunityId, MyOpportunityRequestVerify request) + private async Task PerformActionSendForVerification(User user, Guid opportunityId, MyOpportunityRequestVerify request, VerificationMethod? requiredVerificationMethod) { ArgumentNullException.ThrowIfNull(request, nameof(request)); @@ -1128,12 +1127,15 @@ private async Task PerformActionSendForVerificationManual(User user, Guid opport if (!opportunity.VerificationEnabled) throw new ValidationException($"Opportunity '{opportunity.Title}' can not be completed / verification is not enabled"); - if (opportunity.VerificationMethod == null || opportunity.VerificationMethod != VerificationMethod.Manual) - throw new ValidationException($"Opportunity '{opportunity.Title}' can not be completed / requires verification method manual"); + if (!opportunity.VerificationMethod.HasValue) + throw new DataInconsistencyException($"Data inconsistency detected: The opportunity '{opportunity.Title}' has verification enabled, but no verification method is set"); - if (opportunity.VerificationTypes == null || opportunity.VerificationTypes.Count == 0) + if (opportunity.VerificationMethod == VerificationMethod.Manual && (opportunity.VerificationTypes == null || opportunity.VerificationTypes.Count == 0)) throw new DataInconsistencyException("Manual verification enabled but opportunity has no mapped verification types"); + if (requiredVerificationMethod.HasValue && opportunity.VerificationMethod != requiredVerificationMethod.Value) + throw new ValidationException($"Opportunity '{opportunity.Title}' cannot be completed / requires verification method {requiredVerificationMethod}"); + if (request.DateStart.HasValue && request.DateStart.Value < opportunity.DateStart) throw new ValidationException($"Start date can not be earlier than the opportunity start date of '{opportunity.DateStart:yyyy-MM-dd}'"); @@ -1185,7 +1187,7 @@ private async Task PerformActionSendForVerificationManual(User user, Guid opport myOpportunity.DateStart = request.DateStart; myOpportunity.DateEnd = request.DateEnd; - await PerformActionSendForVerificationManualProcessVerificationTypes(request, opportunity, myOpportunity, isNew); + await PerformActionSendForVerificationProcessVerificationTypes(request, opportunity, myOpportunity, isNew); //used by notifications myOpportunity.UserPhoneNumber = user.PhoneNumber; @@ -1196,7 +1198,7 @@ private async Task PerformActionSendForVerificationManual(User user, Guid opport myOpportunity.ZltoReward = opportunity.ZltoReward; myOpportunity.YomaReward = opportunity.YomaReward; - if (request.InstantVerification) return; //with instant-verifications verification pending notifications are not sent + if (request.InstantVerification || opportunity.VerificationMethod == VerificationMethod.Automatic) return; //with instant-verifications or automatic verification pending notifications are not sent //sent to youth await SendNotification(myOpportunity, NotificationType.Opportunity_Verification_Pending); @@ -1254,7 +1256,7 @@ NotificationType.Opportunity_Verification_Completed or } } - private async Task PerformActionSendForVerificationManualProcessVerificationTypes(MyOpportunityRequestVerify request, + private async Task PerformActionSendForVerificationProcessVerificationTypes(MyOpportunityRequestVerify request, Opportunity.Models.Opportunity opportunity, Models.MyOpportunity myOpportunity, bool isNew) @@ -1289,7 +1291,7 @@ await _executionStrategyService.ExecuteInExecutionStrategyAsync(async () => } //new items - await PerformActionSendForVerificationManualProcessVerificationTypes(request, opportunity, myOpportunity, itemsNewBlobs); + await PerformActionSendForVerificationProcessVerificationTypes(request, opportunity, myOpportunity, itemsNewBlobs); //delete existing items in blob storage (deleted in db above) foreach (var item in itemsExisting) @@ -1321,7 +1323,7 @@ await _executionStrategyService.ExecuteInExecutionStrategyAsync(async () => } } - private async Task PerformActionSendForVerificationManualProcessVerificationTypes(MyOpportunityRequestVerify request, + private async Task PerformActionSendForVerificationProcessVerificationTypes(MyOpportunityRequestVerify request, Opportunity.Models.Opportunity opportunity, Models.MyOpportunity myOpportunity, List itemsNewBlobs)