using System;
using System.Text;
using System.Data;
using System.Linq;
using System.Web.Mvc;
using System.Data.Entity.Validation;
using System.Collections.Generic;
using Neo.Afx.Mvc;
using Neo.Afx.ViewModels;
using Neo.Afx.ComponentModel;
using Neo.Afx.Services.Integration;
using Neo.Afx.Services.Workflows;
using Neo.Afx.Services.Workflows.Entities;
using Neo.Afx.Services.Forms.Entities;
using Neo.LegitimateLicences.Common;
using Neo.LegitimateLicences.Data.Models;
using Neo.LegitimateLicences.LicenceIssueRequest.Models;
using Neo.LegitimateLicences.Data;
using Neo.Afx.Services.Audits;
using Neo.LegitimateLicences.Common.Helpers;
using Neo.LegitimateLicences.LicenceIssueRequest.Models.Database;
using LicenceIssueDetail = Neo.LegitimateLicences.LicenceIssueRequest.Models.Database.LicenceIssueDetail;
using Vehicle = Neo.LegitimateLicences.LicenceIssueRequest.Models.Database.Vehicle;
using Licence = Neo.LegitimateLicences.LicenceIssueRequest.Models.Database.Licence;
using Member = Neo.LegitimateLicences.LicenceIssueRequest.Models.Database.Member;
using Neo.Afx.Common;
using Neo.LegitimateLicences.Logic.eNatis;
using System.Configuration;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Net.Http.Headers;
using System.Xml;
using Newtonsoft.Json;
using System.Net;
using Neo.LegitimateLicences.ApplicationRequest.Models.Database;
namespace Neo.LegitimateLicences.LicenceIssueRequest
{
public partial class LicenceIssueRequestController
{
#region Save
///
/// Save LicenceIssueRequest details
///
///
///
[HttpPost]
public JsonNetResult Save(LicenceIssueDetail value)
{
var changes = new List();
var memberChanges = new List();
var licenceChanges = new List();
var vehicleChanges = new List();
var auditDb = new AuditsDatabase();
var legitimateDb = new LegitimateDatabase();
// eNatis Variables
bool eNatisCheckValid = false;
var loginResult = string.Empty;
var eNatisCheckErrorMessage = string.Empty;
var appDb = new ApplicationDatabase(); //don't use database context as we need the values refreshed every time
var legitimateDatabase = new LegitimateDatabase();
if (value.LicenceIssueDetailID == Database.NewID)
{
throw new Exception("#EXPOSE_ERROR#New licence records can not be created manually.");
}
else
{
try
{
var liDb = new LicenceIssueDatabase(); //don't use database as we need the values refreshed every time
var existingModel = liDb.GetLicenceIssueForm(value.LicenceIssueDetailID, Profile.UserID);
//if (existingModel != null && existingModel.UpdatedDate.ToLongTimeString() != value.UpdatedDate.ToLongTimeString())
//{
// return new JsonNetResult()
// {
// Data = new JsonBaseResult()
// {
// Success = false,
// ErrorCode = "CONFLICT",
// Error = "#EXPOSE_ERROR#The record has been modified by someone else. Please try again."
// }
// };
//}
value.UpdatedDate = DateTime.Now;
value.UpdatedByID = Profile.UserID;
#region Get active user task
var itemId = WebHelper.ItemID;
var taskId = WebHelper.TaskID;
Vw_WorkflowInstanceTask activeUserTask = null;
if (taskId != -1)
{
activeUserTask = WorkflowHelper.GetActiveUserUserTaskByID((long)EntityEnum.LicenceIssueDetails, itemId, taskId, Profile.UserID);
}
if (activeUserTask == null)
{
activeUserTask = WorkflowHelper.GetActiveUserTask(Profile.UserID, EntityEnum.LicenceIssueDetails, itemId);
}
#endregion
#region Check if can save
var canSubmit = (activeUserTask != null && activeUserTask.WorkflowTemplateStepTypeID == (short)WorkflowTemplateStepTypeEnum.DataCapture);
if (!canSubmit)
{
throw new Exception("#EXPOSE_ERROR#Security error");
}
#endregion
//make sure the VIN number is not linked to any other licence before saving
if (value.Licence != null && value.Vehicle != null && !string.IsNullOrEmpty(value.Vehicle.VIN))
{
// Throws if the VIN is linked to ANOTHER licence (existing behaviour). This
// does NOT catch a VIN that exists in Verified.Vehicles but is linked to no
// licence (an orphan left by a previous partial save).
try
{
legitimateDb.CheckIfvehicleIsInUse(value.Licence.OperatingLicenceNumber, value.Vehicle.VIN, Profile.UserID);
}
catch (Exception)
{
// The SP flags a VIN that's on another licence but doesn't name it. Enrich
// the message with the licence number(s) when we can; otherwise rethrow as-is.
var inUseLicences = Database.GetLicencesLinkedByVIN(value.Vehicle.VIN, Profile.UserID);
if (!string.IsNullOrEmpty(inUseLicences))
{
throw new Exception("#EXPOSE_ERROR#The vehicle (chassis " + value.Vehicle.VIN
+ ") is already in use on licence(s): " + inUseLicences
+ ". Please delink it from that licence before using it here.");
}
throw;
}
// Duplicate-VIN guard. Covers BOTH inserting a new vehicle AND editing an
// existing vehicle's VIN to one already held by a DIFFERENT row. Conflict =
// a row with this VIN exists and it isn't this same vehicle (NewID never matches
// a real id, so new captures always qualify; an unchanged VIN matches self and is
// skipped). Without this the save hits IX_Vehicles_VIN and surfaces only as a
// generic "An error has occurred".
var existingVehicleId = Database.GetVehicleIdByExactVIN(value.Vehicle.VIN);
if (existingVehicleId != 0 && existingVehicleId != value.Vehicle.VehicleID)
{
// 1. Linked to a verified licence -> tell the user to delink (existing flow).
var linkedLicences = Database.GetLicencesLinkedByVIN(value.Vehicle.VIN, Profile.UserID);
if (!string.IsNullOrEmpty(linkedLicences))
{
throw new Exception("#EXPOSE_ERROR#The chassis number (" + value.Vehicle.VIN
+ ") is already linked to the following licence(s): " + linkedLicences
+ ". Please delink the vehicle from that licence before proceeding.");
}
// 2. Not on a verified licence, but reserved by a running licence-issue or
// application workflow -> it's spoken for, NOT an orphan. Report it as linked
// to another licence rather than attempting a (futile) cleanse.
var inProgress = Database.GetVehicleInProgressUsage(value.Vehicle.VIN);
if (!string.IsNullOrEmpty(inProgress))
{
throw new Exception("#EXPOSE_ERROR#The chassis number (" + value.Vehicle.VIN
+ ") is already linked to another licence that is currently being processed (" + inProgress
+ "). That licence must be completed or cancelled, or the vehicle delinked there, before it can be used here.");
}
// 3. True orphan: exists but linked to nothing. Fast targeted release, then retry.
// Best-effort: a failure here must not surface as a generic error.
try { Database.CleanseUnusedVehicleData(value.Vehicle.VIN); } catch { /* best-effort release */ }
// Safety net: if the row somehow survived the cleanse, don't tell the user to
// "Save again" (it would loop). Surface a clear message instead.
if (Database.GetVehicleIdByExactVIN(value.Vehicle.VIN) != 0)
{
throw new Exception("#EXPOSE_ERROR#The chassis number (" + value.Vehicle.VIN
+ ") already exists and could not be automatically released. Please contact support.");
}
throw new Exception("#EXPOSE_ERROR#The chassis number (" + value.Vehicle.VIN
+ ") already existed in the system but was not linked to any licence. "
+ "An automatic cleanup has been run to release it - please click Save again to continue.");
}
}
// Make sure the object we are saving is the same is in the current context
var current = Database.Context.LicenceIssueDetails.Where(a => a.LicenceIssueDetailID == value.LicenceIssueDetailID).FirstOrDefault();
Database.Context.Entry(current).CurrentValues.SetValues(value);
Database.Context.Entry(current).State = System.Data.Entity.EntityState.Modified;
#region Vehicle
current.Vehicle = value.Vehicle;
// do eNatis check on vehicle here [DS20250117]
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(current.Vehicle.RegistrationNumber, false);
switch (queryResult.ErrorMessage)
{
case "NOT_LOGGED_IN":
loginResult = eNatisLogin();
break;
case "FIELD_INVALID":
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(value.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 = (current.Vehicle.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 = (current.Vehicle.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 = (current.Vehicle.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 = (current.Vehicle.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."
}
};
}
}
}
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" } });
}
}
// check if any errors has occured
if (!string.IsNullOrEmpty(eNatisCheckErrorMessage))
{
return new JsonNetResult()
{
Data = new JsonBaseResult()
{
Success = false,
ErrorCode = "ERROR",
Error = eNatisCheckErrorMessage
}
};
}
if (eNatisCheckValid && string.IsNullOrEmpty(eNatisCheckErrorMessage))
{
// Normalise the identifier fields before save so what we store matches
// what eNatis returns (uppercase, no surrounding whitespace).
if (current.Vehicle != null)
{
current.Vehicle.VIN = (current.Vehicle.VIN ?? string.Empty).Trim().ToUpperInvariant();
current.Vehicle.EngineNumber = (current.Vehicle.EngineNumber ?? string.Empty).Trim().ToUpperInvariant();
current.Vehicle.RegistrationNumber = (current.Vehicle.RegistrationNumber ?? string.Empty).Trim().ToUpperInvariant();
}
if (current.Vehicle == null || current.Vehicle.VehicleID == Database.NewID)
{
Database.Insert(current.Vehicle);
changes.Add(string.Format("{0}, Added vehicle {1}, {2}", "Vehicles", current.Vehicle.VIN, current.Vehicle.RegistrationNumber));
}
else
{
Database.Update(current.Vehicle);
vehicleChanges.AddRange(auditDb.GetEntityDifferences(existingModel.Vehicle, current.Vehicle, "VehicleID"));
}
// VehicleMake/Model/TypeDescription 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.
Database.Save();
liDb.UpdateVehicleENatisDescriptions(
current.Vehicle.VehicleID,
current.Vehicle.VehicleMakeDescription,
current.Vehicle.VehicleModelDescription,
current.Vehicle.VehicleTypeDescription);
}
#endregion
#region Licence
if (value.Application.ApplicationRequestTypeID == (short)Neo.LegitimateLicences.Common.ApplicationRequestTypeEnum.TemporarySpecialLicence)
{
//this logic must be updated in the APP save as well if you adjust it
if (((TimeSpan)(value.Licence.DateOfExpiry - value.Licence.DateOfIssue)).TotalDays > 14)
{
throw new Exception("#EXPOSE_ERROR#A special licence duration is not allowed to exceed 14 days.");
}
else if (value.Licence.DateOfExpiry.Value < value.Licence.DateOfIssue)
{
throw new Exception("#EXPOSE_ERROR#The issue date must be before or the same as the expiry date.");
}
Database.Update(value.Licence);
licenceChanges.AddRange(auditDb.GetEntityDifferences(existingModel.Licence, value.Licence, "LicenceID"));
}
else
{
// Normal (non-temporary) licences: clerk picks DateOfExpiry on the
// Licence Issuing Details screen. DateOfIssue is stamped at print, not here.
// DateOfExpiry stays editable for the life of the licence, so the clerk
// can correct mistakes or extend validity even after first print.
if (value.Licence != null && value.Licence.DateOfExpiry.HasValue)
{
if (value.Licence.DateOfExpiry.Value.Date < DateTime.Today)
{
throw new Exception("#EXPOSE_ERROR#Date of Expiry cannot be in the past.");
}
if (value.Licence.DateOfExpiry != existingModel.Licence.DateOfExpiry)
{
liDb.UpdateLicenceDateOfExpiry(value.Licence.LicenceID, value.Licence.DateOfExpiry);
licenceChanges.Add(string.Format("DateOfExpiry changed from {0} to {1}",
existingModel.Licence.DateOfExpiry, value.Licence.DateOfExpiry));
}
}
}
#endregion
#region Member
Database.Update(value.Licence.Member);
memberChanges.AddRange(auditDb.GetEntityDifferences(existingModel.Licence.Member, value.Licence.Member, "MemberID"));
#endregion
#region Licence Issue Detail
Database.Update(current);
changes.AddRange(auditDb.GetEntityDifferences(existingModel, value, "LicenceIssueDetailID"));
#endregion
#region Do save
try
{
Database.Save();
Database.LicenceIssuePostSave(value.LicenceIssueDetailID, Profile.UserID);
if (changes.Count > 0)
{
auditDb.WriteEntries((short)AuditSourceEnum.Application, (short)AuditActionEnum.Updated, changes, (short)EntityEnum.LicenceIssueDetails, value.LicenceIssueDetailID, Profile.UserID);
}
if (memberChanges.Count > 0)
{
auditDb.WriteEntries((short)AuditSourceEnum.Application, (short)AuditActionEnum.Updated, memberChanges, (short)EntityEnum.LicenceIssueDetails, value.LicenceIssueDetailID, Profile.UserID);
auditDb.WriteEntries((short)AuditSourceEnum.Application, (short)AuditActionEnum.Updated, memberChanges, (short)EntityEnum.Members, value.Licence.MemberID, Profile.UserID);
}
if (licenceChanges.Count > 0)
{
auditDb.WriteEntries((short)AuditSourceEnum.Application, (short)AuditActionEnum.Updated, licenceChanges, (short)EntityEnum.LicenceIssueDetails, value.LicenceIssueDetailID, Profile.UserID);
auditDb.WriteEntries((short)AuditSourceEnum.Application, (short)AuditActionEnum.Updated, licenceChanges, (short)EntityEnum.OperatingLicences, value.Licence.LicenceID, Profile.UserID);
}
if (vehicleChanges.Count > 0)
{
auditDb.WriteEntries((short)AuditSourceEnum.Application, (short)AuditActionEnum.Updated, vehicleChanges, (short)EntityEnum.LicenceIssueDetails, value.LicenceIssueDetailID, Profile.UserID);
if (value.Licence != null && value.Licence.VehicleID.HasValue)
{
auditDb.WriteEntries((short)AuditSourceEnum.Application, (short)AuditActionEnum.Updated, vehicleChanges, (short)EntityEnum.Vehicles, value.Licence.VehicleID.Value, 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("value", value);
#endregion
var errorMessage = ErrorHelper.ProcessError(ex, "LicenceIssueRequest", "Save", additionalData);
#endregion
var message = (ex.InnerException != null && ex.InnerException.InnerException != null) ? ex.InnerException.InnerException.Message : ex.Message;
if (!string.IsNullOrEmpty(message) && message.ToLower().Contains("ix_vehicles"))
{
var chassis = (value.Vehicle != null && !string.IsNullOrEmpty(value.Vehicle.VIN)) ? value.Vehicle.VIN : "blank";
var linkedLicences = Database.GetLicencesLinkedByVIN(value.Vehicle.VIN, Profile.UserID);
if (string.IsNullOrEmpty(linkedLicences))
{
// Defensive: a VIN orphaned between the pre-insert guard and this
// insert. Release just this VIN now (fast) so the retry succeeds.
try { Database.CleanseUnusedVehicleData(value.Vehicle.VIN); } catch { /* best-effort release */ }
message = "The specified chassis number (" + chassis + ") could not be saved because a conflicting unlinked vehicle record was present. An automatic cleanup has been run - please try saving again.";
}
else
{
message = "The specified chassis number (" + chassis + ") is already linked to the following application(s): " + Database.GetLicencesLinkedByVIN(value.Vehicle.VIN, Profile.UserID);
}
}
return new JsonNetResult()
{
Data = new JsonBaseResult()
{
Success = false,
ErrorCode = "ERROR",
Error = string.Format("Error: {0}", message)
}
};
}
#endregion
}
catch (Exception ex)
{
#region Error Handling
#region Error Additional Data
Dictionary additionalData = new Dictionary();
additionalData.Add("value", value);
#endregion
var errorMessage = ErrorHelper.ProcessError(ex, "LicenceIssueRequest", "Save", additionalData);
#endregion
return new JsonNetResult()
{
Data = new JsonBaseResult()
{
Success = false,
ErrorCode = "ERROR",
Error = errorMessage
}
};
}
return new JsonNetResult()
{
Data = new JsonBaseResult()
{
Success = true,
Error = "",
ErrorCode = ""
}
};
}
}
#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;
}
}
#if FALSE
private string eNatisLoginLegacy()
{
// 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);
// 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);
HttpStatusCode? httpStatusCode = null;
string responseContent = string.Empty;
try
{
// Perform the POST request
var response = client.PostAsync(loginURL, content).Result;
httpStatusCode = 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;
}
}
}
#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 LicenceIssueRequesteNatisModel 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 LicenceIssueRequesteNatisModel eNatisVehicleQueryInternal(string vehicleRegistrationNumber, bool cookieCheck, string searchType, string cookie)
{
var model = new LicenceIssueRequesteNatisModel();
// 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))
{
// 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;
}
}
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(LicenceIssueRequesteNatisModel 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;
}
}
///
/// 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 (LicenceIssue) 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" } });
}
}
}
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)
{
// Standardize to 6 digits by adding leading zeros if needed,
// to match how eNatis usually pads the sequence
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
}
}