using Neo.Afx.Common; using Neo.Afx.Security; using Neo.Afx.Services.Audits; using Neo.Afx.Services.Integration; using Neo.Afx.Services.Workflows.Entities; using Neo.Afx.ViewModels; using Neo.LegitimateLicences.ApplicationRequest.Models; using Neo.LegitimateLicences.ApplicationRequest.Models.Database; using Neo.LegitimateLicences.Common; using Neo.LegitimateLicences.Common.Helpers; using Neo.LegitimateLicences.Data.Models; using Neo.LegitimateLicences.Logic.eNatis; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Configuration; using System.Data.Entity.Validation; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Web.Mvc; using System.Xml; namespace Neo.LegitimateLicences.ApplicationRequest { public partial class ApplicationRequestController { #region LicenceVehicle /// /// Return the licence vehicle dialog /// /// /// /// public ActionResult LicenceVehicle(long applicationDetailID, long applicationLicenceID) { #region Get active user task Vw_WorkflowInstanceTask activeUserTask = null; activeUserTask = WorkflowHelper.GetActiveUserTask(Profile.UserID, EntityEnum.ApplicationRequestDetails, applicationDetailID); #endregion var model = new ApplicationRequestFormModel { UserSecurables = (Profile.Securables != null) ? Profile.Securables.ToList() : new List(), Ui = new WorkflowConsoleUIProperties() }; var applicationDatabase = new ApplicationDatabase(); var vehicle = applicationDatabase.GetLicenceVehicle(applicationLicenceID, Profile.UserID); var application = applicationDatabase.GetApplicationView(applicationDetailID, Profile.UserID); var vehicleOptional = "true"; if ( application.ApplicationRequestTypeID == (short)ApplicationRequestTypeEnum.ReplacementsCOV || application.ApplicationRequestTypeID == (short)ApplicationRequestTypeEnum.RenewalAndCOV || application.ApplicationRequestTypeID == (short)ApplicationRequestTypeEnum.RenewalCOVAndIncreaseInCarryingCapacity || application.ApplicationRequestTypeID == (short)ApplicationRequestTypeEnum.RenewalDeceasedTransferIncreaseInCarryingCapacityAndCOV || application.ApplicationRequestTypeID == (short)ApplicationRequestTypeEnum.IncreaseInCarryingCapacityAndCOV || application.ApplicationRequestTypeID == (short)ApplicationRequestTypeEnum.RenewalNormalTransferAndCOV || application.ApplicationRequestTypeID == (short)ApplicationRequestTypeEnum.RenewalAndDeceasedTransferAndCOV ) { vehicleOptional = "false"; vehicle.IsStillToBeAcquired = false; } model.Data = vehicle; model.ApplicationView = application; model.Lookups = GetVehicleLookups(); model.WorkflowInstance = WorkflowHelper.GetWorkflowInstanceView(WebHelper.EntityID, WebHelper.ItemID, Profile.UserID); if (model.ApplicationView.CopiedFromApplicationDetailID != null) { model.ParentApplicationView = Database.GetApplicationView(model.ApplicationView.CopiedFromApplicationDetailID.Value, Profile.UserID); } else { model.ParentApplicationView = new Neo.LegitimateLicences.ApplicationRequest.Models.Database.Vw_ApplicationDetails_Light(); } model.SetFormEditState(activeUserTask); model.VehicleOptional = vehicleOptional; return View("Licences.VehicleDetails", model); } #endregion #region LicencePreviousVehicle /// /// Return the licence previous vehicle documents /// /// /// /// public ActionResult LicencePreviousVehicle(long applicationDetailID, long applicationLicenceID) { #region Get active user task Vw_WorkflowInstanceTask activeUserTask = null; activeUserTask = WorkflowHelper.GetActiveUserTask(Profile.UserID, EntityEnum.ApplicationRequestDetails, applicationDetailID); #endregion var model = new ApplicationRequestFormModel { UserSecurables = (Profile.Securables != null) ? Profile.Securables.ToList() : new List(), Ui = new WorkflowConsoleUIProperties() }; var applicationDatabase = new ApplicationDatabase(); var vehicle = applicationDatabase.GetLicencePreviousVehicle(applicationLicenceID, Profile.UserID); var application = applicationDatabase.GetApplicationView(applicationDetailID, Profile.UserID); model.Data = vehicle; model.ApplicationView = application; model.Lookups = GetPrevouisVehicleLookups(); model.WorkflowInstance = WorkflowHelper.GetWorkflowInstanceView(WebHelper.EntityID, WebHelper.ItemID, Profile.UserID); if (model.ApplicationView.CopiedFromApplicationDetailID != null) { model.ParentApplicationView = Database.GetApplicationView(model.ApplicationView.CopiedFromApplicationDetailID.Value, Profile.UserID); } else { model.ParentApplicationView = new Neo.LegitimateLicences.ApplicationRequest.Models.Database.Vw_ApplicationDetails_Light(); } model.SetFormEditState(activeUserTask); return View("Licences.PreviousVehicleDetails", model); } #endregion #region LicenceVehicleDocuments /// /// Return the licence vehicle documents dialog /// /// /// /// public ActionResult LicenceVehicleDocuments(long applicationDetailID, long applicationLicenceID) { #region Get active user task Vw_WorkflowInstanceTask activeUserTask = null; activeUserTask = WorkflowHelper.GetActiveUserTask(Profile.UserID, EntityEnum.ApplicationRequestDetails, applicationDetailID); #endregion var model = new ApplicationRequestFormModel { UserSecurables = (Profile.Securables != null) ? Profile.Securables.ToList() : new List(), Ui = new WorkflowConsoleUIProperties() }; var applicationDatabase = new ApplicationDatabase(); var vehicle = applicationDatabase.GetLicenceVehicle(applicationLicenceID, Profile.UserID); var services = applicationDatabase.GetLicenceServiceDetails(applicationLicenceID, Profile.UserID); var application = applicationDatabase.GetApplicationView(applicationDetailID, Profile.UserID); model.Data = vehicle; model.ApplicationView = application; model.WorkflowInstance = WorkflowHelper.GetWorkflowInstanceView(WebHelper.EntityID, WebHelper.ItemID, Profile.UserID); Boolean isUnscheduledService = false; if (services.Services.Where(a => a.ServiceTypeID == (short)ServiceTypeEnum.Unscheduled).Count() > 0) { isUnscheduledService = true; } model.IsUnscheduledService = isUnscheduledService; if (model.ApplicationView.CopiedFromApplicationDetailID != null) { model.ParentApplicationView = Database.GetApplicationView(model.ApplicationView.CopiedFromApplicationDetailID.Value, Profile.UserID); } else { model.ParentApplicationView = new Neo.LegitimateLicences.ApplicationRequest.Models.Database.Vw_ApplicationDetails_Light(); } model.SetFormEditState(activeUserTask); return View("Licences.VehicleDocuments", model); } #endregion #region GetLookups /// /// Get lookup lists used on vehicle details /// /// public List GetVehicleLookups() { var lookups = new List(); var integrationStore = new SqlIntegrationStore(); try { integrationStore.Open(); lookups.Add(new LookupList("VehicleTypes", integrationStore.GetDataEntities("VehicleTypes", string.Empty).OrderBy(a => a.Title))); lookups.Add(new LookupList("CarryingCapacities", integrationStore.GetDataEntities("CarryingCapacities", string.Empty))); lookups.Add(new LookupList("VehicleModels", integrationStore.GetDataEntities("VehicleModels", string.Empty))); } finally { integrationStore.Close(); } return lookups; } public List GetPrevouisVehicleLookups() { var lookups = new List(); var integrationStore = new SqlIntegrationStore(); try { integrationStore.Open(); lookups.Add(new LookupList("VehicleTypes", integrationStore.GetDataEntities("VehicleTypes", string.Empty).OrderBy(a => a.Title))); lookups.Add(new LookupList("CarryingCapacities", integrationStore.GetDataEntities("CarryingCapacities", string.Empty))); lookups.Add(new LookupList("VehicleModels", integrationStore.GetDataEntities("VehicleModels", string.Empty))); lookups.Add(new LookupList("VehicleMakes", integrationStore.GetDataEntities("VehicleMakes", string.Empty))); } finally { integrationStore.Close(); } return lookups; } #endregion #region SaveLicenceVehicle /// /// Save details for licence vehicle record /// /// /// /// [HttpPost] public JsonNetResult SaveLicenceVehicle(long applicationDetailID, ApplicationLicenceVehicleDetail value) { var changes = new List(); var auditDb = new AuditsDatabase(); bool eNatisCheckValid = false; var loginResult = string.Empty; var eNatisCheckErrorMessage = string.Empty; try { #region Check if can save var model = GetModel(); if (!model.Ui.canSubmit) { throw new Exception("#EXPOSE_ERROR#Security error"); } if (!model.CanEditNewVehicleDocuments) { throw new Exception("#EXPOSE_ERROR#Security error"); } #endregion var appDb = new ApplicationDatabase(); //don't use database context as we need the values refreshed every time var legitimateDatabase = new LegitimateDatabase(); #region eNatis Check try { // check to make sure the cookie is still valid var cookieCheck = eNatisCookieVerification(); switch (cookieCheck) { case "VALID": break; case "INVALID": try { loginResult = eNatisLogin(); } catch(Exception ex) { return new JsonNetResult() { Data = new JsonBaseResult() { Success = false, ErrorCode = "ERROR", Error = "Unable to connect to eNatis at the moment. Please check your internet connection and try again.If the issue persists, please contact support." } }; } if (loginResult == "SUCCESS") { cookieCheck = "VALID"; } else if (loginResult == "MAX_SESSIONS_IN_USE") { eNatisCheckErrorMessage += "eNatis has reached the maximum number of active sessions. Please try again later or contact support if the issue persists."; } break; } if (cookieCheck == "VALID") { try { var queryResult = eNatisVehicleQuery(value.VehicleRegistrationNumber, false); switch (queryResult.ErrorMessage) { case "NOT_LOGGED_IN": loginResult = eNatisLogin(); break; case "FIELD_INVALID": // Update the existing model to set eNatisCheckPassed to false var invalidVehicleModel = appDb.GetLicenceVehicle(value.ApplicationLicenceID, Profile.UserID); // Get a fresh copy of the model to update var freshModel = appDb.GetLicenceVehicle(value.ApplicationLicenceID, Profile.UserID); freshModel.eNatisCheckPassed = false; try { Database.Update(freshModel); changes.AddRange(auditDb.GetEntityDifferences(invalidVehicleModel, freshModel, "ApplicationLicenceID")); Database.Save(); System.Diagnostics.Debug.WriteLine($"Successfully updated eNatisCheckPassed to false for vehicle {value.ApplicationLicenceID} - Vehicle Registration Number not found on eNatis"); if (changes.Count > 0) { auditDb.WriteEntries((short)AuditSourceEnum.Application, (short)AuditActionEnum.Updated, changes, (short)EntityEnum.ApplicationtDetails, applicationDetailID, Profile.UserID); } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Error updating eNatisCheckPassed: {ex.Message}"); // Continue with the error response even if the update fails } eNatisCheckErrorMessage += "Entered Vehicle Registration Number not found on eNatis. Please rectify."; break; } if (queryResult != null && string.IsNullOrEmpty(eNatisCheckErrorMessage)) { // get the operator details, to validate ID no against vehicle var applicationApplicant = appDb.GetApplicationApplicant(applicationDetailID, Profile.UserID); // make sure applicant details have been retrieved before doing comparison if (applicationApplicant != null) { // check the eNatis results against what user has captured // if any does not match, stop the process and do not save // ID Number Check - Use slash detection for business registrations bool idNumberMatches = false; if (applicationApplicant.IDNumberOrBusinessRegistrationNo.Contains("/")) { // If system ID has a slash, it's a business - use flexible matching idNumberMatches = DoBusinessRegNumbersMatch( applicationApplicant.IDNumberOrBusinessRegistrationNo, queryResult.OwnerIdNo ); } else if (queryResult.IdDocType == "RSA ID document") { // For RSA ID documents, do exact string comparison idNumberMatches = applicationApplicant.IDNumberOrBusinessRegistrationNo == queryResult.OwnerIdNo; } else { // For all other types, fall back to exact comparison idNumberMatches = applicationApplicant.IDNumberOrBusinessRegistrationNo == queryResult.OwnerIdNo; } if (idNumberMatches) { eNatisCheckValid = true; } else { eNatisCheckValid = false; eNatisCheckErrorMessage += $"Owner ID or Business Registration number mismatch. System: [{applicationApplicant.IDNumberOrBusinessRegistrationNo}], eNatis: [{queryResult.OwnerIdNo}]. Please rectify.
"; } // Vehicle VIN Check (case-insensitive, trimmed — eNatis returns uppercase // while capturers may type any case). string capturedVin = (value.VIN ?? string.Empty).Trim(); string eNatisVin = (queryResult.VinOrChassis ?? string.Empty).Trim(); if (capturedVin.Length > 0 && string.Equals(capturedVin, eNatisVin, StringComparison.OrdinalIgnoreCase)) { eNatisCheckValid = eNatisCheckValid && true; } else { eNatisCheckValid = false; eNatisCheckErrorMessage += $"VIN or Chassis Number mismatch. System: [{capturedVin}], eNatis: [{eNatisVin}]. Please rectify.
"; } // Vehicle Engine Number Check (case-insensitive, trimmed). string capturedEngine = (value.EngineNumber ?? string.Empty).Trim(); string eNatisEngine = (queryResult.EngineNo ?? string.Empty).Trim(); if (capturedEngine.Length > 0 && string.Equals(capturedEngine, eNatisEngine, StringComparison.OrdinalIgnoreCase)) { eNatisCheckValid = eNatisCheckValid && true; } else { eNatisCheckValid = false; eNatisCheckErrorMessage += $"Engine Number mismatch. System: [{capturedEngine}], eNatis: [{eNatisEngine}]. Please rectify.
"; } // Vehicle Make Check (string comparison against VehicleMakeDescription // captured on the form — no more FK lookup since Make is free-text now). string capturedMake = (value.VehicleMakeDescription ?? "").Trim(); string eNatisMake = (queryResult.VehicleMake ?? "").Trim(); if (capturedMake.Length > 0 && string.Equals(capturedMake, eNatisMake, StringComparison.OrdinalIgnoreCase)) { eNatisCheckValid = eNatisCheckValid && true; } else { eNatisCheckValid = false; eNatisCheckErrorMessage += $"Vehicle Make mismatch. System: [{capturedMake}], eNatis: [{eNatisMake}]. Please rectify.
"; } // Vehicle Model Check (string comparison against VehicleModelDescription). string capturedModel = (value.VehicleModelDescription ?? "").Trim(); string eNatisModel = (queryResult.VehicleModel ?? "").Trim(); if (capturedModel.Length > 0 && string.Equals(capturedModel, eNatisModel, StringComparison.OrdinalIgnoreCase)) { eNatisCheckValid = eNatisCheckValid && true; } else { eNatisCheckValid = false; eNatisCheckErrorMessage += $"Vehicle Model mismatch. System: [{capturedModel}], eNatis: [{eNatisModel}]. Please rectify.
"; } } } } catch (Exception ex) { return new JsonNetResult() { Data = new JsonBaseResult() { Success = false, ErrorCode = "ERROR", Error = "Unable to query vehicle details from eNatis at the moment. Please check your internet connection or try again later. If the issue persists, please contact support." } }; } } #endregion eNatis Check var existingModel = appDb.GetLicenceVehicle(value.ApplicationLicenceID, Profile.UserID); //System.Diagnostics.Debug.WriteLine($"Before updating eNatisCheckPassed to false for vehicle {value.ApplicationLicenceID}"); // check if any errors has occured if (!string.IsNullOrEmpty(eNatisCheckErrorMessage)) { // Get a fresh copy of the model to update var freshModel = appDb.GetLicenceVehicle(value.ApplicationLicenceID, Profile.UserID); freshModel.eNatisCheckPassed = false; try { Database.Update(freshModel); changes.AddRange(auditDb.GetEntityDifferences(existingModel, freshModel, "ApplicationLicenceID")); Database.Save(); //System.Diagnostics.Debug.WriteLine($"Successfully updated eNatisCheckPassed to false for vehicle {value.ApplicationLicenceID} - Validation errors: {eNatisCheckErrorMessage}"); if (changes.Count > 0) { auditDb.WriteEntries((short)AuditSourceEnum.Application, (short)AuditActionEnum.Updated, changes, (short)EntityEnum.ApplicationtDetails, applicationDetailID, Profile.UserID); } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Error updating eNatisCheckPassed: {ex.Message}"); // Continue with the error response even if the update fails } return new JsonNetResult() { Data = new JsonBaseResult() { Success = false, ErrorCode = "ERROR", Error = eNatisCheckErrorMessage } }; } if (eNatisCheckValid && string.IsNullOrEmpty(eNatisCheckErrorMessage)) { //System.Diagnostics.Debug.WriteLine($"Before updating eNatisCheckPassed to true for vehicle {value.ApplicationLicenceID}"); // Get a fresh copy of the model to update var freshModel = appDb.GetLicenceVehicle(value.ApplicationLicenceID, Profile.UserID); //System.Diagnostics.Debug.WriteLine($"Current eNatisCheckPassed value in database: {freshModel.eNatisCheckPassed}"); // Set the new value freshModel.eNatisCheckPassed = true; //System.Diagnostics.Debug.WriteLine($"Setting eNatisCheckPassed to true for vehicle {value.ApplicationLicenceID}"); try { // Update the database Database.Update(freshModel); changes.AddRange(auditDb.GetEntityDifferences(existingModel, freshModel, "ApplicationLicenceID")); // Explicitly save changes Database.Save(); //System.Diagnostics.Debug.WriteLine($"Successfully updated eNatisCheckPassed to true for vehicle {value.ApplicationLicenceID}"); // Verify the update by reading the value again var verifyModel = appDb.GetLicenceVehicle(value.ApplicationLicenceID, Profile.UserID); //System.Diagnostics.Debug.WriteLine($"Verified eNatisCheckPassed value in database after update: {verifyModel.eNatisCheckPassed}"); if (changes.Count > 0) { auditDb.WriteEntries((short)AuditSourceEnum.Application, (short)AuditActionEnum.Updated, changes, (short)EntityEnum.ApplicationtDetails, applicationDetailID, Profile.UserID); //System.Diagnostics.Debug.WriteLine($"Audit entries written for vehicle {value.ApplicationLicenceID}"); } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Error updating eNatisCheckPassed: {ex.Message}"); System.Diagnostics.Debug.WriteLine($"Stack trace: {ex.StackTrace}"); throw; // Re-throw the exception to be caught by the outer try-catch } } } finally { // Logout from eNatis after validation is complete to prevent session leakage try { eNatisLogout(); } catch (Exception logoutEx) { // Non-critical error, just log it ErrorHelper.LogError(logoutEx, new Dictionary { { "Method", "eNatisLogout" } }); } } } catch (DbEntityValidationException dbEx) { foreach (var validationError in dbEx.EntityValidationErrors.SelectMany(validationErrors => validationErrors.ValidationErrors)) { return new JsonNetResult() { Data = new JsonBaseResult() { Success = false, ErrorCode = "ERROR", Error = string.Format("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage) } }; } } catch (Exception ex) { #region Error Handling #region Error Additional Data Dictionary additionalData = new Dictionary(); additionalData.Add("applicationDetailID", applicationDetailID); additionalData.Add("value", value); #endregion string error = ErrorHelper.ProcessError(ex, "ApplicationRequest", "SaveLicenceVehicleDocuments", additionalData); #endregion return new JsonNetResult() { Data = new JsonBaseResult() { Success = false, ErrorCode = "ERROR", Error = error } }; } return new JsonNetResult() { Data = new JsonBaseResult() { Success = true } }; } #endregion #region SaveLicenceVehicleDocuments /// /// Save the documents for the licence vehicle record /// /// /// /// [HttpPost] public JsonNetResult SaveLicenceVehicleDocuments(long applicationDetailID, ApplicationLicenceVehicleDetail value) { var changes = new List(); var auditDb = new AuditsDatabase(); try { #region Check if can save var model = GetModel(); if (!model.Ui.canSubmit) { throw new Exception("#EXPOSE_ERROR#Security error"); } if (!model.CanEditNewVehicleDocuments) { throw new Exception("#EXPOSE_ERROR#Security error"); } #endregion var appDb = new ApplicationDatabase(); //don't use database context as we need the values refreshed every time var existingModel = appDb.GetLicenceVehicle(value.ApplicationLicenceID, Profile.UserID); // If "Still to be acquired" is selected, set eNatisCheckPassed to NULL if (value.IsStillToBeAcquired) { value.eNatisCheckPassed = null; // Clear document IDs since documents are not required for "Still to be acquired" value.RoadworthyCertificateDocID = null; value.CertificateOfRegistrationDocID = null; value.ProfessionalDriversPermitDocID = null; value.CertifiedServiceRecordsDocID = null; value.CertifiedPublicPassangerLiabilityDocID = null; } else { // Preserve the eNatisCheckPassed value from the existing model value.eNatisCheckPassed = existingModel.eNatisCheckPassed; } // Normalise the identifier fields before save so what we store matches // what eNatis returns (uppercase, no surrounding whitespace). value.VIN = (value.VIN ?? string.Empty).Trim().ToUpperInvariant(); value.EngineNumber = (value.EngineNumber ?? string.Empty).Trim().ToUpperInvariant(); value.VehicleRegistrationNumber = (value.VehicleRegistrationNumber ?? string.Empty).Trim().ToUpperInvariant(); Database.Update(value); changes.AddRange(auditDb.GetEntityDifferences(existingModel, value, "ApplicationLicenceID")); Database.Save(); // VehicleModelDescription / VehicleTypeDescription aren't EF-mapped yet // (added by DBUp script 109; .edmx still to be regenerated). Persist them // via a direct UPDATE so the form's values survive the save. appDb.UpdateVehicleENatisDescriptions(value.ApplicationLicenceID, value.VehicleModelDescription, value.VehicleTypeDescription); if (changes.Count > 0) { auditDb.WriteEntries((short)AuditSourceEnum.Application, (short)AuditActionEnum.Updated, changes, (short)EntityEnum.ApplicationtDetails, applicationDetailID, Profile.UserID); } } catch (DbEntityValidationException dbEx) { foreach (var validationError in dbEx.EntityValidationErrors.SelectMany(validationErrors => validationErrors.ValidationErrors)) { return new JsonNetResult() { Data = new JsonBaseResult() { Success = false, ErrorCode = "ERROR", Error = string.Format("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage) } }; } } catch (Exception ex) { #region Error Handling #region Error Additional Data Dictionary additionalData = new Dictionary(); additionalData.Add("applicationDetailID", applicationDetailID); additionalData.Add("value", value); #endregion string error = ErrorHelper.ProcessError(ex, "ApplicationRequest", "SaveLicenceVehicleDocuments", additionalData); #endregion return new JsonNetResult() { Data = new JsonBaseResult() { Success = false, ErrorCode = "ERROR", Error = error } }; } return new JsonNetResult() { Data = new JsonBaseResult() { Success = true } }; } /// /// Reads vehicle details from eNatis without performing the mismatch validation, so the /// data capturer can auto-populate the Vehicle Details form. Lookup key is one of /// "registration" / "vin" / "engine". /// [HttpPost] public JsonNetResult LookupVehicleFromENatis(string searchValue, string searchType) { if (string.IsNullOrWhiteSpace(searchValue)) { return new JsonNetResult { Data = new JsonBaseResult { Success = false, ErrorCode = "ERROR", Error = "Please enter a value to lookup." } }; } var normalisedType = (searchType ?? "registration").ToLower(); if (normalisedType != "registration" && normalisedType != "vin" && normalisedType != "engine") { return new JsonNetResult { Data = new JsonBaseResult { Success = false, ErrorCode = "ERROR", Error = "Invalid lookup type." } }; } try { // Reuse the current session if still valid; otherwise login. var cookieState = eNatisCookieVerification(); if (cookieState == "INVALID") { var loginResult = eNatisLogin(); if (loginResult != "SUCCESS") { var userMsg = loginResult == "MAX_SESSIONS_IN_USE" ? "eNatis has reached the maximum number of active sessions. Please try again later." : "Unable to authenticate with eNatis. Please try again or contact support."; return new JsonNetResult { Data = new JsonBaseResult { Success = false, ErrorCode = "ERROR", Error = userMsg } }; } } var result = eNatisVehicleQuery(searchValue, cookieCheck: false, searchType: normalisedType); if (!string.IsNullOrEmpty(result.ErrorMessage)) { var userMsg = result.ErrorMessage == "FIELD_INVALID" ? "Vehicle not found on eNatis for the supplied value." : $"eNatis lookup failed: {result.ErrorMessage}"; return new JsonNetResult { Data = new JsonBaseResult { Success = false, ErrorCode = "ERROR", Error = userMsg } }; } ResolveLookupIDs(result); return new JsonNetResult { Data = new { Success = true, Vehicle = result } }; } catch (Exception ex) { // Temporary diagnostic log to App_Data/eNatis-lookup.log so we can read the // actual exception without Sentry access. Safe to remove once the intermittent // failure is diagnosed. try { var logPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "App_Data", "eNatis-lookup.log"); System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(logPath)); System.IO.File.AppendAllText(logPath, $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} LookupVehicleFromENatis searchType={searchType} searchValue={searchValue}{Environment.NewLine}" + $" {ex.GetType().FullName}: {ex.Message}{Environment.NewLine}" + $"{ex.StackTrace}{Environment.NewLine}{Environment.NewLine}"); } catch { /* never let the diagnostic log break the response */ } ErrorHelper.LogError(ex, new Dictionary { { "Method", "LookupVehicleFromENatis" } }); return new JsonNetResult { Data = new JsonBaseResult { Success = false, ErrorCode = "ERROR", Error = "Unable to query vehicle details from eNatis at the moment. Please try again or contact support." } }; } finally { try { eNatisLogout(); } catch (Exception logoutEx) { ErrorHelper.LogError(logoutEx, new Dictionary { { "Method", "LookupVehicleFromENatis.Logout" } }); } } } #endregion #region eNatis Check private static string loginURL = ConfigurationManager.AppSettings["eNatisLoginURL"]; private static string eNatisQueryURL = ConfigurationManager.AppSettings["eNatisQueryURL"]; /// /// Cookie proxy that reads from the shared . Kept /// for legacy call sites that referenced the instance field directly. /// private string _cookie { get { return ENatisSession.Cookie; } } public string eNatisLogin() { try { ENatisSession.EnsureSession(); return "SUCCESS"; } catch (MaxSessionsInUseException) { return "MAX_SESSIONS_IN_USE"; } catch (Exception ex) { ErrorHelper.LogError(ex, new Dictionary { { "Method", "eNatisLogin" } }); throw; } } // Legacy eNatisLogin body kept below for reference during refactor; not compiled. #if FALSE private string eNatisLoginLegacy() { string certificatePath = string.Empty; string responseContent = string.Empty; int? httpStatusCode = null; try { // Set up HttpClientHandler with the certificate var handler = new WebRequestHandler { ClientCertificateOptions = ClientCertificateOption.Manual }; // Load the certificate with proper key storage flags for server environments certificatePath = ConfigurationManager.AppSettings["eNatisCertificatePath"]; string certificatePassword = ConfigurationManager.AppSettings["eNatisCertificatePassword"]; // Validate certificate path exists if (string.IsNullOrEmpty(certificatePath)) { throw new Exception("eNatisCertificatePath is not configured in web.config"); } if (!System.IO.File.Exists(certificatePath)) { throw new Exception($"Certificate file not found at path: {certificatePath}. Please verify the certificate path is correct and the file is accessible on the server."); } // Load certificate with MachineKeySet and PersistKeySet flags for IIS/server compatibility X509Certificate2 certificate; try { certificate = new X509Certificate2(certificatePath, certificatePassword, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet); } catch (Exception certEx) { throw new Exception($"Failed to load certificate from {certificatePath}. Error: {certEx.Message}. Please verify the certificate file is valid and the password is correct.", certEx); } handler.ClientCertificates.Add(certificate); // Support both TLS 1.1 and TLS 1.2 for eNatis connectivity System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12 | System.Net.SecurityProtocolType.Tls11; System.Net.ServicePointManager.ServerCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => true; // The XML content to send in the POST request var xmlContent = $@" {ConfigurationManager.AppSettings["eNatisOpModInd"]} {ConfigurationManager.AppSettings["eNatisESTxanID"]} {ConfigurationManager.AppSettings["eNatisESUserN"]} {ConfigurationManager.AppSettings["eNatisESUID"]} {ConfigurationManager.AppSettings["eNatisEStermID"]} {ConfigurationManager.AppSettings["eNatisUsername"]} {ConfigurationManager.AppSettings["eNatisPassword"]} {ConfigurationManager.AppSettings["eNatisTxanType"]} "; var content = new StringContent(xmlContent, Encoding.UTF8, "application/xml"); using (var client = new HttpClient(handler)) { // Example: Add Authorization token if needed var username = ConfigurationManager.AppSettings["eNatisUsername"]; var password = ConfigurationManager.AppSettings["eNatisPassword"]; var credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{username}:{password}")); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials); try { // Perform the POST request var response = client.PostAsync(loginURL, content).Result; httpStatusCode = (int)response.StatusCode; // Read the response content early for logging if needed responseContent = response.Content.ReadAsStringAsync().Result; // Check if the response contains the "Set-Cookie" header if (response.Headers.Contains("Set-Cookie")) { // Retrieve the cookies from the "Set-Cookie" header var cookies = response.Headers.GetValues("Set-Cookie"); foreach (var c in cookies) { _cookie = c; } } // Load the XML into an XmlDocument XmlDocument xmlDoc = new XmlDocument(); xmlDoc.LoadXml(responseContent); // Convert the XmlDocument to a JSON string string json = JsonConvert.SerializeXmlNode(xmlDoc); // Parse the JSON into a dynamic object dynamic jsonObj = JsonConvert.DeserializeObject(json); //Get the status of the request var requestState = jsonObj.X4000Resp.TxState.Value; return requestState; } catch (Exception ex) { // Log detailed error for troubleshooting var additionalData = new Dictionary(); additionalData.Add("loginURL", loginURL); additionalData.Add("certificatePath", certificatePath); additionalData.Add("HttpStatusCode", httpStatusCode?.ToString() ?? "N/A"); additionalData.Add("ResponseContent", responseContent ?? "N/A"); ErrorHelper.LogError(ex, new Dictionary { { "Method", "eNatisLogin" } }, additionalData); // Re-throw with a more descriptive message if it's a connection issue if (ex.Message.Contains("SSL/TLS") || ex.Message.Contains("secure channel") || ex.InnerException?.Message.Contains("SSL/TLS") == true) { throw new Exception("#EXPOSE_ERROR#Failed to establish a secure connection to eNatis (TLS error). Please ensure the server supports TLS 1.2 and the certificate is valid.", ex); } throw; } } } catch (Exception ex) { // Log error details for debugging //System.Diagnostics.Debug.WriteLine($"[eNatisLogin] Error occurred: {ex.Message}"); //System.Diagnostics.Debug.WriteLine($"[eNatisLogin] Stack Trace: {ex.StackTrace}"); //System.Diagnostics.Debug.WriteLine($"[eNatisLogin] Login URL: {loginURL}"); //System.Diagnostics.Debug.WriteLine($"[eNatisLogin] Certificate Path: {certificatePath}"); //System.Diagnostics.Debug.WriteLine($"[eNatisLogin] HTTP Status Code: {httpStatusCode?.ToString() ?? "N/A"}"); //System.Diagnostics.Debug.WriteLine($"[eNatisLogin] Response Content: {responseContent}"); // Log to Sentry with additional context var additionalData = new Dictionary(); additionalData.Add("eNatisLoginURL", loginURL); additionalData.Add("eNatisCertificatePath", certificatePath); additionalData.Add("HttpStatusCode", httpStatusCode?.ToString() ?? "N/A"); additionalData.Add("ResponseContent", responseContent ?? "N/A"); additionalData.Add("HasCookie", !string.IsNullOrEmpty(_cookie)); var customTags = new Dictionary { { "Class", "ApplicationRequestController" }, { "Method", "eNatisLogin" }, { "Component", "eNatisIntegration" } }; ErrorHelper.LogError(ex, customTags, additionalData); // Re-throw the exception so calling code can handle it throw; } } #endif public string eNatisLogout() { ENatisSession.ForceLogout(); return "SUCCESS"; } #if FALSE private string eNatisLogoutLegacy() { if (string.IsNullOrEmpty(_cookie)) { return "NO_SESSION"; } // Create the cookie container and add the session cookie CookieContainer cookieContainer = new CookieContainer(); try { int startIndex = _cookie.IndexOf("="); int endIndex = _cookie.IndexOf(";", startIndex + 1); if (endIndex == -1) endIndex = _cookie.Length; string cookieValue = _cookie.Substring(startIndex + 1, endIndex - startIndex - 1); cookieContainer.Add(new Uri(loginURL), new Cookie("JSESSIONID", cookieValue)); } catch { /* Ignore cookie parsing errors */ } // Set up HttpClientHandler with the certificate and cookie container var handler = new WebRequestHandler { ClientCertificateOptions = ClientCertificateOption.Manual, CookieContainer = cookieContainer }; // Load the certificate string certificatePath = ConfigurationManager.AppSettings["eNatisCertificatePath"]; string certificatePassword = ConfigurationManager.AppSettings["eNatisCertificatePassword"]; X509Certificate2 certificate = new X509Certificate2(certificatePath, certificatePassword, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet); handler.ClientCertificates.Add(certificate); // Support both TLS 1.1 and TLS 1.2 System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12 | System.Net.SecurityProtocolType.Tls11; System.Net.ServicePointManager.ServerCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => true; // The XML content to send in the POST request (CLOSE transaction) var xmlContent = $@" {ConfigurationManager.AppSettings["eNatisOpModInd"]} {ConfigurationManager.AppSettings["eNatisESTxanID"]} {ConfigurationManager.AppSettings["eNatisESUserN"]} {ConfigurationManager.AppSettings["eNatisESUID"]} {ConfigurationManager.AppSettings["eNatisEStermID"]} {ConfigurationManager.AppSettings["eNatisUsername"]} {ConfigurationManager.AppSettings["eNatisPassword"]} CLOSE "; var content = new StringContent(xmlContent, Encoding.UTF8, "application/xml"); using (var client = new HttpClient(handler)) { var username = ConfigurationManager.AppSettings["eNatisUsername"]; var password = ConfigurationManager.AppSettings["eNatisPassword"]; var credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{username}:{password}")); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials); try { var response = client.PostAsync(loginURL, content).Result; _cookie = string.Empty; // Clear the cookie after logout return "SUCCESS"; } catch (Exception ex) { ErrorHelper.LogError(ex, new Dictionary { { "Method", "eNatisLogout" } }); return "ERROR"; } } } #endif public ApplicationRequesteNatisModel eNatisVehicleQuery(string vehicleRegistrationNumber, bool cookieCheck, string searchType = "registration") { // eNatis is case-sensitive on registration / VIN / engine numbers and rejects // whitespace. Normalise once at the entry point so the rest of the code (and // the tag-variant retry loop) only deals with canonical values. var normalised = (vehicleRegistrationNumber ?? string.Empty).Trim().ToUpperInvariant(); // Run the eNatis vehicle query under the shared session gate. ENatisSession // owns login/logout lifecycle and serialises traffic so we never exceed // one concurrent eNatis session. return ENatisSession.WithSession(cookie => { return eNatisVehicleQueryInternal(normalised, cookieCheck, searchType, cookie); }); } private ApplicationRequesteNatisModel eNatisVehicleQueryInternal(string vehicleRegistrationNumber, bool cookieCheck, string searchType, string cookie) { var model = new ApplicationRequesteNatisModel(); // Create the cookie container and add a cookie CookieContainer cookieContainer = new CookieContainer(); int startIndex = cookie.IndexOf("="); int endIndex = cookie.IndexOf(";", startIndex + 1); if (endIndex == -1) endIndex = cookie.Length; cookieContainer.Add(new Uri(eNatisQueryURL), new Cookie("JSESSIONID", cookie.Substring(startIndex + 1, endIndex - startIndex - 1))); // Set up HttpClientHandler with the certificate var handler = new WebRequestHandler { ClientCertificateOptions = ClientCertificateOption.Manual }; // Load the certificate with proper key storage flags for server environments string certificatePath = ConfigurationManager.AppSettings["eNatisCertificatePath"]; string certificatePassword = ConfigurationManager.AppSettings["eNatisCertificatePassword"]; // Validate certificate path exists if (string.IsNullOrEmpty(certificatePath)) { throw new Exception("eNatisCertificatePath is not configured in web.config"); } if (!System.IO.File.Exists(certificatePath)) { throw new Exception($"Certificate file not found at path: {certificatePath}. Please verify the certificate path is correct and the file is accessible on the server."); } // Load certificate with MachineKeySet and PersistKeySet flags for IIS/server compatibility X509Certificate2 certificate; try { certificate = new X509Certificate2(certificatePath, certificatePassword, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet); } catch (Exception certEx) { throw new Exception($"Failed to load certificate from {certificatePath}. Error: {certEx.Message}. Please verify the certificate file is valid and the password is correct.", certEx); } handler.ClientCertificates.Add(certificate); handler.CookieContainer = cookieContainer; // Support both TLS 1.1 and TLS 1.2 for eNatis connectivity System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12 | System.Net.SecurityProtocolType.Tls11; System.Net.ServicePointManager.ServerCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => true; using (var client = new HttpClient(handler)) // Ensure handler isn't disposed prematurely { // Phase 2k: try each tag variant for the chosen search type until eNatis returns // vehicle data. Captures the case where a number plate the user typed is stored // under MVRegN (or MRegN) rather than LicN, etc. var variants = TagVariantsFor(searchType); dynamic jsonObj = null; foreach (var tag in variants) { string xmlContent = BuildLookupXml(tag, vehicleRegistrationNumber); jsonObj = SendXmlRequest(xmlContent, client); // Cookie check just needs any response back — don't retry across tags. if (cookieCheck) break; // Did this variant find a vehicle? If so, stop. if (HasVehicleData(jsonObj)) break; } // Drive the populate branch off actual vehicle-data presence, not a // TxState blocklist — eNatis can return TxStates we don't know about // and used to NRE on jsonObj.X1001Resp.Vehicle.VehicleDet (null) when // we incorrectly assumed it was populated. if (HasVehicleData(jsonObj)) { // valid query, proceed with comparison //Set model values with returned data model.MVRegN = jsonObj.X1001Resp.Vehicle.VehicleDet.MVRegN; model.VinOrChassis = jsonObj.X1001Resp.Vehicle.VehicleDet.VinOrChassis; model.EngineNo = jsonObj.X1001Resp.Vehicle.VehicleDet.EngineN; model.VehicleModel = jsonObj.X1001Resp.Vehicle.VehicleDet.ModelName.Desc; model.VehicleMake = jsonObj.X1001Resp.Vehicle.VehicleDet.Make.Desc; model.OwnerIdNo = jsonObj.X1001Resp.Vehicle.Owner.PerDet.PerId.IdDocN; // Capture the ID document type from the eNatis response model.IdDocType = jsonObj.X1001Resp.Vehicle.Owner.PerDet.PerId.IdDocType.Desc; // Owner name (BusOrSurname). Reference-only on the capture form so // the capturer can spot a mismatch before clicking Save. model.OwnerName = GetJsonString(jsonObj?.X1001Resp?.Vehicle?.Owner?.PerDet?.PerId, "BusOrSurname"); // Additional fields used by the auto-populate (Lookup-from-eNatis) flow. // Field paths confirmed against a real X1001 response (HINO Bus 2008). var det = jsonObj?.X1001Resp?.Vehicle?.VehicleDet; model.LicN = GetJsonString(det, "LicN", "MVRegN", "MRegN"); // Vehicle Description = " - " so an inspector // sees both the regulatory category AND the body type // (e.g. "Light passenger mv (less than 12 persons) - Hatch back"). // Falls back gracefully if either side is missing. var mvCat = GetJsonString(det, "MVCat.Desc", "VehCat.Desc"); var mvDesc = GetJsonString(det, "MVDesc.Desc", "BodyType"); if (!string.IsNullOrWhiteSpace(mvCat) && !string.IsNullOrWhiteSpace(mvDesc)) model.VehicleType = mvCat.Trim() + " - " + mvDesc.Trim(); else model.VehicleType = mvCat ?? mvDesc; // YearOfManufacture: prefer ManufYear; fall back to year of MVLicFirstD (first-licensed date). model.YearOfManufacture = GetJsonString(det, "ManufYear", "ManufYr", "YearOfBuild") ?? ExtractYearFromDate(GetJsonString(det, "MVLicFirstD")); model.GrossMass = GetJsonString(det, "GVM", "GVWR", "PermGVWR", "GrossMass", "GrossWeight"); model.Tare = GetJsonString(det, "Tare", "TareWt", "UnladenMass"); model.SeatedCapacity = GetJsonString(det, "CapSit", "SeatCap", "StCap"); model.StandingCapacity = GetJsonString(det, "CapStand", "StandCap"); model.CarryingCapacity = GetJsonString(det, "CarrCap", "CapCarry", "LoadCap"); } else { // Null-safe TxState read. eNatis returns `FIELD_INVALID` — // Json.NET converts this to a JValue string ("FIELD_INVALID"), not an object // with a Value child. Some responses do wrap with attributes giving // { "Value": "..." }, so we try both shapes. string txState = null; try { var tsNode = jsonObj?.X4000Resp?.TxState; if (tsNode != null) { try { txState = (string)tsNode.Value; } catch { } if (string.IsNullOrWhiteSpace(txState)) try { txState = (string)tsNode; } catch { } } } catch { } if (txState == "FIELD_INVALID" && cookieCheck) { model.CookieCheckPassed = true; } else { model.ErrorMessage = string.IsNullOrEmpty(txState) ? "EMPTY_RESPONSE" : txState; model.CookieCheckPassed = false; } } } // **Handler and client get disposed here** return model; } public string eNatisCookieVerification() { // ENatisSession owns the cookie lifecycle (login on first use, refresh // on idle timeout). If a cookie is cached, treat it as valid — the next // eNatisVehicleQuery call will re-login automatically if it has expired. return string.IsNullOrEmpty(ENatisSession.Cookie) ? "INVALID" : "VALID"; } // Phase 2k: candidate tag names per search type. eNatis stores a vehicle's // "registration-like" identifier across LicN / MVRegN / MRegN inconsistently — // a value the capturer types might match any of them. We try each in turn. private static readonly Dictionary _eNatisTagVariants = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "registration", new[] { "LicN", "MVRegN", "MRegN" } }, { "vin", new[] { "VinOrChassis", "VinN" } }, { "engine", new[] { "EngineN", "EngN" } } }; private static string[] TagVariantsFor(string searchType) { string[] result; return _eNatisTagVariants.TryGetValue(searchType ?? "registration", out result) ? result : _eNatisTagVariants["registration"]; } private static bool HasVehicleData(dynamic jsonObj) { try { var det = jsonObj?.X1001Resp?.Vehicle?.VehicleDet; if (det == null) return false; var lic = det.LicN?.ToString(); var reg = det.MVRegN?.ToString(); return !string.IsNullOrWhiteSpace(lic) || !string.IsNullOrWhiteSpace(reg); } catch { return false; } } private string BuildLookupXml(string tagName, string value) { return $@" {ConfigurationManager.AppSettings["eNatisOpModInd"]} {ConfigurationManager.AppSettings["eNatisESTxanID"]} {ConfigurationManager.AppSettings["eNatisESUserN"]} {ConfigurationManager.AppSettings["eNatisESUID"]} {ConfigurationManager.AppSettings["eNatisEStermID"]} <{tagName}>{value} "; } private static string GetJsonString(dynamic obj, params string[] propertyChains) { if (obj == null) return null; foreach (var chain in propertyChains) { try { dynamic node = obj; foreach (var part in chain.Split('.')) { if (node == null) break; node = node[part]; } var s = node?.ToString(); if (!string.IsNullOrWhiteSpace(s)) return s; } catch { } } return null; } private static string ExtractYearFromDate(string dateString) { if (string.IsNullOrWhiteSpace(dateString)) return null; DateTime d; if (DateTime.TryParse(dateString, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out d)) return d.Year.ToString(); return null; } private void ResolveLookupIDs(ApplicationRequesteNatisModel m) { var legDb = new LegitimateDatabase(); if (!string.IsNullOrWhiteSpace(m.VehicleMake)) { var make = legDb.GetVehicleMakes().FirstOrDefault(x => x.Description != null && x.Description.Trim().Equals(m.VehicleMake.Trim(), StringComparison.OrdinalIgnoreCase)); m.VehicleMakeID = make?.VehicleMakeID; if (m.VehicleMakeID.HasValue && !string.IsNullOrWhiteSpace(m.VehicleModel)) { var model = legDb.GetVehicleModels().FirstOrDefault(x => x.VehicleMakeID == m.VehicleMakeID && x.Description != null && x.Description.Trim().Equals(m.VehicleModel.Trim(), StringComparison.OrdinalIgnoreCase)); m.VehicleModelID = model?.VehicleModelID; } } if (!string.IsNullOrWhiteSpace(m.VehicleType)) { var vt = legDb.GetVehicleTypes().FirstOrDefault(x => x.Description != null && x.Description.Trim().Equals(m.VehicleType.Trim(), StringComparison.OrdinalIgnoreCase)); m.VehicleTypeID = vt?.VehicleTypeID; } if (!string.IsNullOrWhiteSpace(m.CarryingCapacity)) { var cc = legDb.GetVehicleCarryingCapacities().FirstOrDefault(x => x.Description != null && x.Description.Trim().Equals(m.CarryingCapacity.Trim(), StringComparison.OrdinalIgnoreCase)); m.CarryingCapacityID = cc?.CarryingCapacityID; } } private dynamic SendXmlRequest(string xmlContent, HttpClient client) { var content = new StringContent(xmlContent, Encoding.UTF8, "application/xml"); var username = ConfigurationManager.AppSettings["eNatisUsername"]; var password = ConfigurationManager.AppSettings["eNatisPassword"]; var credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{username}:{password}")); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials); var response = client.PostAsync(eNatisQueryURL, content).Result; var responseContent = response.Content.ReadAsStringAsync().Result; XmlDocument xmlDoc = new XmlDocument(); xmlDoc.LoadXml(responseContent); string json = JsonConvert.SerializeXmlNode(xmlDoc); return JsonConvert.DeserializeObject(json); } #endregion eNatis Check #region ID Validation Helpers /// /// Standardizes a business registration number by removing non-alphanumeric characters /// /// The registration number to standardize /// Standardized registration number private string StandardizeBusinessRegNumber(string registrationNumber) { if (string.IsNullOrEmpty(registrationNumber)) return string.Empty; // Remove all non-alphanumeric characters return System.Text.RegularExpressions.Regex.Replace(registrationNumber, "[^a-zA-Z0-9]", ""); } /// /// Extracts the year from a business registration number /// /// The registration number to extract from /// Whether the number is from eNatis format /// The extracted year private string ExtractYear(string registrationNumber, bool isENatis) { if (string.IsNullOrEmpty(registrationNumber)) return string.Empty; try { if (isENatis) { // For letter-prefixed format (e.g. F17...) if (registrationNumber.Length >= 3 && char.IsLetter(registrationNumber[0])) return registrationNumber.Substring(1, 2); // For numeric-only format (e.g. 98...) if (registrationNumber.Length >= 2) return registrationNumber.Substring(0, 2); } else { // For standard format (e.g. 2017/390392/07), extract year before first slash int slashIndex = registrationNumber.IndexOf('/'); if (slashIndex > 0) { string fullYear = registrationNumber.Substring(0, slashIndex); if (fullYear.Length >= 2) return fullYear.Substring(fullYear.Length - 2); } } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Error extracting year: {ex.Message}"); } return string.Empty; } /// /// Extracts the core registration number from a business registration number /// /// The registration number to extract from /// Whether the number is from eNatis format /// The extracted core registration number private string ExtractCoreRegistrationNumber(string registrationNumber, bool isENatis) { if (string.IsNullOrEmpty(registrationNumber)) return string.Empty; try { if (isENatis) { // 1. Try letter-prefixed format (e.g. F173903920050) var match = System.Text.RegularExpressions.Regex.Match(registrationNumber, @"[A-Z]\d{2}(\d{6})"); if (match.Success && match.Groups.Count > 1) return match.Groups[1].Value; // 2. Try numeric-only format (e.g. 9858431230011) var numericMatch = System.Text.RegularExpressions.Regex.Match(registrationNumber, @"^\d{2}(\d+)"); if (numericMatch.Success && numericMatch.Groups.Count > 1) { return numericMatch.Groups[1].Value; } } else { // For standard format (e.g. 2017/390392/07), extract between slashes var match = System.Text.RegularExpressions.Regex.Match(registrationNumber, @"/(\d+)/"); if (match.Success && match.Groups.Count > 1) { return match.Groups[1].Value.PadLeft(6, '0'); } } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Error extracting core number: {ex.Message}"); } return string.Empty; } /// /// Checks if two business registration numbers match /// /// The number from our system /// The number from eNatis /// True if the numbers match based on year and core number private bool DoBusinessRegNumbersMatch(string systemNumber, string eNatisNumber) { if (string.IsNullOrEmpty(systemNumber) || string.IsNullOrEmpty(eNatisNumber)) return false; // Equality short-circuit if (systemNumber == eNatisNumber) return true; try { string systemYear = ExtractYear(systemNumber, false); string eNatisYear = ExtractYear(eNatisNumber, true); bool yearMatches = !string.IsNullOrEmpty(systemYear) && !string.IsNullOrEmpty(eNatisYear) && systemYear == eNatisYear; string systemCoreNumber = ExtractCoreRegistrationNumber(systemNumber, false).TrimStart('0'); string eNatisCoreNumber = ExtractCoreRegistrationNumber(eNatisNumber, true).TrimStart('0'); bool coreNumberMatches = !string.IsNullOrEmpty(systemCoreNumber) && !string.IsNullOrEmpty(eNatisCoreNumber) && eNatisCoreNumber.StartsWith(systemCoreNumber); return yearMatches && coreNumberMatches; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Error in business reg comparison: {ex.Message}"); return false; } } #endregion ID Validation Helpers } }