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}{tagName}>
";
}
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
}
}