using System.Linq; using System.Web.Mvc; using System; using Neo.LegitimateLicences.Common; using Neo.LegitimateLicences.Common.Helpers; using Neo.Afx.Services.Workflows; using Neo.LegitimateLicences.Web.Models.Afx; using System.Collections.Generic; using Neo.LegitimateLicences.Data.Models; using Neo.Afx.Services.Workflows.Entities; using Neo.LegitimateLicences.Common; namespace Neo.LegitimateLicences.Web.Controllers { public partial class AfxController { // Typed link holder for provider -> application mapping private sealed class ProviderAppLink { public int EntityID { get; set; } // 1000 public long ItemID { get; set; } // ApplicationDetailID } #region Submit public ActionResult Submit(long entityID, long itemID, long? taskID) { return View("~/Views/Shared/Afx/Actions/Submit.cshtml", GetWorkflowTaskActionModel("Submit", entityID, itemID, taskID)); } #endregion #region Approve public ActionResult Approve(long entityID, long itemID, long? taskID) { return View("~/Views/Shared/Afx/Actions/Approve.cshtml", GetWorkflowTaskActionModel("Approve", entityID, itemID, taskID)); } #endregion #region Reject public ActionResult Reject(long entityID, long itemID, long? taskID) { return View("~/Views/Shared/Afx/Actions/Reject.cshtml", GetWorkflowTaskActionModel("Reject", entityID, itemID, taskID)); } #endregion #region Cancel public ActionResult Cancel(long entityID, long itemID, long? taskID) { return View("~/Views/Shared/Afx/Actions/Cancel.cshtml", GetWorkflowTaskActionModel("Cancel", entityID, itemID, taskID)); } #endregion #region RequestChange public ActionResult RequestChange(long entityID, long itemID, long? taskID) { return View("~/Views/Shared/Afx/Actions/RequestChange.cshtml", GetWorkflowTaskActionModel("Request a Change", entityID, itemID, taskID)); } #endregion #region Escalate public ActionResult Escalate(long entityID, long itemID, long? taskID) { return View("~/Views/Shared/Afx/Actions/Escalate.cshtml", GetWorkflowTaskActionModel("Escalate", entityID, itemID, taskID)); } #endregion #region ReturnEscalation public ActionResult ReturnEscalation(long entityID, long itemID, long? taskID) { return View("~/Views/Shared/Afx/Actions/ReturnEscalation.cshtml", GetWorkflowTaskActionModel("ReturnEscalation", entityID, itemID, taskID)); } #endregion #region GetWorkflowTaskActionModel protected AfxTaskActionModel GetWorkflowTaskActionModel(string action, long entityID, long itemID, long? taskID) { var activeTask = (!taskID.HasValue || taskID == -1) ? WorkflowHelper.GetActiveUserTask(Profile.UserID, entityID, itemID) : WorkflowHelper.GetActiveUserUserTaskByID(entityID, itemID, taskID.Value, Profile.UserID); var db = new WorkflowsDatabase(); // If invoked from the Provider context (entity 1046), resolve the related Application workflow (entity 1000) // so that Submit actions behave like Accreditation/OL (workflow runs on Application) long effectiveEntityID = entityID; long effectiveItemID = itemID; if (entityID == (long)EntityEnum.PlatformProviders) { using (var coreDb = new Neo.LegitimateLicences.Data.Models.LegitimateDatabase()) { var link = coreDb.Context.Database.SqlQuery(@" SELECT TOP 1 CAST(1000 AS INT) AS EntityID, /*Applications*/ AD.ApplicationDetailID AS ItemID FROM App.ApplicationDetails AD INNER JOIN App.ApplicationApplicantDetails AAD ON AAD.ApplicationDetailID = AD.ApplicationDetailID INNER JOIN Wf.WorkflowInstances WI ON WI.WorkflowInstanceID = AD.WorkflowInstanceID INNER JOIN Verified.PlatformProviders PP ON PP.PlatformProviderID = @PlatformProviderID WHERE AD.ApplicationRequestTypeID IN (60,61,62) /*App provider request types*/ AND ( (NULLIF(LTRIM(RTRIM(PP.BusinessRegNumber)), '') IS NOT NULL AND LTRIM(RTRIM(ISNULL(AAD.IDNumberOrBusinessRegistrationNo, ''))) = LTRIM(RTRIM(PP.BusinessRegNumber))) OR (NULLIF(LTRIM(RTRIM(PP.TradingName)), '') IS NOT NULL AND LTRIM(RTRIM(ISNULL(AAD.TradeName, ''))) = LTRIM(RTRIM(PP.TradingName))) OR (NULLIF(LTRIM(RTRIM(PP.LegalName)), '') IS NOT NULL AND LTRIM(RTRIM(ISNULL(AAD.SurnameOrCompanyName, ''))) = LTRIM(RTRIM(PP.LegalName))) ) ORDER BY WI.UpdatedDate DESC, WI.CreatedDate DESC", new System.Data.SqlClient.SqlParameter("@PlatformProviderID", itemID)).FirstOrDefault(); if (link != null) { effectiveEntityID = link.EntityID; effectiveItemID = link.ItemID; } else { // No related application found; keep provider context which will result in no submit // (same outcome Accreditation would show when no workflow exists) } } } var submitToNextStepChecks = new List(); if (activeTask != null) { submitToNextStepChecks = db.PerformCanSubmitToNextStepChecks(activeTask.WorkflowInstanceID, activeTask.WorkflowInstanceTaskID, Profile.UserID); } // AppProviderIssueRequest-specific override: Only two prechecks like Accreditation (certificate printed, documents complete) // EntityID 1042 = AppProviderIssueRequest, itemID = AppProviderIssueDetailID // Also ensure task is assigned to a valid user before submitting (prevents FK constraint errors) if (entityID == (long)EntityEnum.AppProviderIssueRequest) { var outcomes = new List(); var coreDb = new LegitimateDatabase(); // Resolve PlatformProviderID from AppProviderIssueDetailID var platformProviderID = coreDb.Context.Database.SqlQuery( @"SELECT TOP 1 PC.PlatformProviderID FROM App.AppProviderIssueDetails APID INNER JOIN Verified.ProviderCertificates PC ON PC.ProviderCertificateID = APID.VerifiedProviderCertificateID WHERE APID.AppProviderIssueDetailID = @p0", new object[] { itemID }).FirstOrDefault(); if (platformProviderID.HasValue) { // 1) Certificate printed (Verified.Printouts, non-test, provider certificate type 8) var printed = coreDb.Context.Database.SqlQuery( @"SELECT TOP 1 1 FROM Verified.Printouts WHERE EntityID = @p0 AND ItemID = @p1 AND PrintoutTypeID = 8 AND ISNULL(IsTestPrint,0) = 0 ORDER BY DatePrinted DESC", new object[] { (int)EntityEnum.PlatformProviders, platformProviderID.Value }).Any(); outcomes.Add(new SubmitToNextStepOutcome { Title = "Has the certificate been printed?", IsSuccessfull = printed, OutcomeDetails = printed ? "" : "No successful certificate print found.", RequiredForSubmitting = true }); // 2) Documents complete (all required docs verified passed) // Use the issuing workflow (EntityID 1042) for documents var wfView = db.GetWorkflowInstanceView(entityID, itemID); var documents = (wfView != null) ? db.GetWorkflowInstanceDocuments(wfView.WorkflowInstanceID, Profile.UserID) : new List(); // consider only entity-bound docs (not editable titles) and require status = 2 (passed) var docsComplete = documents != null && documents.Where(d => !d.IsDocumentTitleEditable).All(d => d.DocumentVerificationStatusID == 2); outcomes.Add(new SubmitToNextStepOutcome { Title = "Are all required documents verified?", IsSuccessfull = docsComplete, OutcomeDetails = docsComplete ? "" : "There are documents awaiting verification or failed verification.", RequiredForSubmitting = true }); } else { // No provider certificate found - add a check that will fail outcomes.Add(new SubmitToNextStepOutcome { Title = "Has the certificate been printed?", IsSuccessfull = false, OutcomeDetails = "Provider certificate not found for this issue request.", RequiredForSubmitting = true }); } submitToNextStepChecks = outcomes; } // Provider-specific override: Only two prechecks like Accreditation (certificate printed, documents complete) if (entityID == (long)EntityEnum.PlatformProviders) { var outcomes = new List(); // 1) Certificate printed (Verified.Printouts, non-test, provider certificate type 8) var coreDb = new LegitimateDatabase(); var printed = coreDb.Context.Database.SqlQuery( @"SELECT TOP 1 1 FROM Verified.Printouts WHERE EntityID = @p0 AND ItemID = @p1 AND PrintoutTypeID = 8 AND ISNULL(IsTestPrint,0) = 0 ORDER BY DatePrinted DESC", new object[] { (int)EntityEnum.PlatformProviders, itemID }).Any(); outcomes.Add(new SubmitToNextStepOutcome { Title = "Has the certificate been printed?", IsSuccessfull = printed, OutcomeDetails = printed ? "" : "No successful certificate print found.", RequiredForSubmitting = true }); // 2) Documents complete (all required docs verified passed) var wfView = db.GetWorkflowInstanceView(entityID, itemID); var documents = (wfView != null) ? db.GetWorkflowInstanceDocuments(wfView.WorkflowInstanceID, Profile.UserID) : new List(); // consider only entity-bound docs (not editable titles) and require status = 2 (passed) var docsComplete = documents != null && documents.Where(d => !d.IsDocumentTitleEditable).All(d => d.DocumentVerificationStatusID == 2); outcomes.Add(new SubmitToNextStepOutcome { Title = "Are all required documents verified?", IsSuccessfull = docsComplete, OutcomeDetails = docsComplete ? "" : "There are documents awaiting verification or failed verification.", RequiredForSubmitting = true }); submitToNextStepChecks = outcomes; // If there is no active task assigned to the user, try to resolve the outstanding group task if (activeTask == null) { var wi = db.GetWorkflowInstance((int)effectiveEntityID, effectiveItemID); if (wi != null) { // Use the view tasks to match the type of activeTask (Vw_WorkflowInstanceTask) var tasksView = db.GetTasks((int)effectiveEntityID, effectiveItemID); // 1 = Outstanding/In Progress activeTask = tasksView?.FirstOrDefault(t => t.WorkflowInstanceTaskStatusID == 1); } } // Ensure AssignedToID is valid for AppProviderIssueRequest tasks before submitting // This prevents FK constraint errors when Pr_WorkflowInstanceTaskAction tries to update/insert records if (activeTask != null && activeTask.WorkflowInstanceTaskID > 0) { // Check if AssignedToID is NULL or invalid (doesn't exist in Security.Users) var taskNeedsAssignment = false; if (activeTask.AssignedToID == null || activeTask.AssignedToID == 0) { taskNeedsAssignment = true; } else { // Verify the UserID exists in Security.Users var userExists = coreDb.Context.Database.SqlQuery( @"SELECT TOP 1 1 FROM Security.Users WHERE UserID = @p0", new object[] { activeTask.AssignedToID }).Any(); if (!userExists) { taskNeedsAssignment = true; } } // Assign to current user if task needs assignment if (taskNeedsAssignment) { try { coreDb.Context.Database.ExecuteSqlCommand( @"UPDATE Wf.WorkflowInstanceTasks SET AssignedToID = @AssignedToID, UpdatedByID = @UpdatedByID, UpdatedDate = GETDATE() WHERE WorkflowInstanceTaskID = @WorkflowInstanceTaskID", new System.Data.SqlClient.SqlParameter("@AssignedToID", Profile.UserID), new System.Data.SqlClient.SqlParameter("@UpdatedByID", Profile.UserID), new System.Data.SqlClient.SqlParameter("@WorkflowInstanceTaskID", activeTask.WorkflowInstanceTaskID)); // Refresh activeTask to reflect the updated AssignedToID activeTask = WorkflowHelper.GetActiveUserTask(Profile.UserID, entityID, itemID); } catch (Exception assignEx) { // Log but don't fail - this is a safety check System.Diagnostics.Debug.WriteLine($"Failed to assign task to user: {assignEx.Message}"); } } } } var role = Profile.Roles.FirstOrDefault(); var isComplete = submitToNextStepChecks.All(o => o.IsSuccessfull); var canSubmit = false; var canApproveReject = false; var canRequestCorrection = false; var canCancel = false; var canEscalate = false; var canReturnEscalation = false; #region CanSubmit if (!submitToNextStepChecks.Any(o => !o.IsSuccessfull && o.RequiredForSubmitting) && activeTask != null) { canSubmit = activeTask.WorkflowTemplateStepTypeID == 2 /*data capture*/ || activeTask.WorkflowTemplateStepTypeID == 5 /*document verification*/; canApproveReject = activeTask.WorkflowTemplateStepTypeID == 1; /*approval*/ } #endregion #region CanRequestCorrection //Ensure that // that the user does have a task assigned // that the workflow is not in step 1 if (activeTask != null && activeTask.WorkflowTemplateStepNumber > 1) { canRequestCorrection = true; } #endregion #region CanCancel //Ensure that // that the user does have a task assigned // that the workflow is not in step 1 if (activeTask != null) { canCancel = true; } #endregion #region CanEscalate List escalateToUsers = new List(); if (activeTask != null) { escalateToUsers = WorkflowHelper.GetEscalateToUsers(activeTask.WorkflowInstanceTaskID, true).ToList(); } if (escalateToUsers == null) { escalateToUsers = new List(); } else { escalateToUsers = escalateToUsers.Where(a => a.UserID != Profile.UserID).ToList(); } //Ensure that // that the user does have a task assigned // that the workflow is not in step 1 if (activeTask != null && escalateToUsers.Any()) { canEscalate = true; } #endregion #region CanReturnEscalation if (activeTask != null) { if (activeTask.EscalatedFromID != null) { canReturnEscalation = true; } } #endregion var model = new AfxTaskActionModel { Outcomes = submitToNextStepChecks.Cast().ToList(), WorkflowInstanceTaskID = (activeTask == null ? -1 : activeTask.WorkflowInstanceTaskID), IsComplete = isComplete, WorkflowID = (activeTask != null ? activeTask.WorkflowID : 0), CanSubmit = canSubmit, CanApproveReject = canApproveReject, CanRequestCorrection = canRequestCorrection, CanCancel = canCancel, CanViewComments = true, CanEscalate = canEscalate, CanReturnEscalation = canReturnEscalation, EscalateToUsers = escalateToUsers }; var wd = new WorkflowsDatabase(); var instance = wd.GetWorkflowInstanceView(model.EntityID, model.ItemID); if (canRequestCorrection) { model.RequestCorrectionSteps = wd.GetWorkflowInstanceTaskStepsForCorrection(activeTask.WorkflowInstanceTaskID); } if (instance != null) { ViewBag.Title = action + " request " + instance.ReferenceNumber + ", " + instance.WorkflowInstanceDescription; model.WorkflowInstanceID = instance.WorkflowInstanceID; } else { ViewBag.Title = action + " request "; } return model; } #endregion } }