diff --git a/app/DynamicsAdapter/DynamicsAdapter.Web/Mapping/Contructors.cs b/app/DynamicsAdapter/DynamicsAdapter.Web/Mapping/Contructors.cs
index 35fcad51..ffec64d5 100644
--- a/app/DynamicsAdapter/DynamicsAdapter.Web/Mapping/Contructors.cs
+++ b/app/DynamicsAdapter/DynamicsAdapter.Web/Mapping/Contructors.cs
@@ -10,7 +10,11 @@ public class Contructors
{
public static SearchRequestEntity ConstructSearchRequestEntity(SearchRequestOrdered src)
{
- if (src?.Person?.Agency?.RequestId == null) throw new ArgumentNullException("SearchRequestOrdered.Person, Agency or RequestID are not allowed Null.");
+ if (src?.Person?.Agency?.RequestId == null)
+ {
+ throw new ArgumentNullException("SearchRequestOrdered.Person, Agency or RequestID are not allowed Null.");
+ }
+
SearchRequestEntity entity = new SearchRequestEntity();
#region agency part
entity.AgentEmail = src.Person.Agency.Email;
@@ -25,12 +29,14 @@ public static SearchRequestEntity ConstructSearchRequestEntity(SearchRequestOrde
else
entity.AgentFax = agencyFax?.PhoneNumber + " -" + agencyFax?.Extension;
}
+
if (src.Person.Agency.Agent != null)
{
Name agentName = src.Person.Agency.Agent;
entity.AgentFirstName = agentName.FirstName;
entity.AgentLastName = agentName.LastName;
}
+
if (src.Person.Agency.InformationRequested != null)
{
foreach (InformationRequested info in src.Person.Agency.InformationRequested)
@@ -55,9 +61,14 @@ public static SearchRequestEntity ConstructSearchRequestEntity(SearchRequestOrde
case InformationRequested.DateOfDeath: entity.DateOfDeathRequested = true; break;
case InformationRequested.IA: entity.IAStatusRequested = true; break;
case InformationRequested.SafetyConcern: entity.SafetyConcernRequested = true; break;
+ case InformationRequested.T1taxform: entity.T1taxform = true; break;
+ case InformationRequested.NoticeofAssessment: entity.NoticeofAssessment = true; break;
+ case InformationRequested.NoticeofReassessment: entity.NoticeofReassessment = true; break;
+ case InformationRequested.FinancialOtherIncome: entity.FinancialOtherIncome = true; break;
};
}
}
+
if (src.Person?.Agency.RequestId != null)
{
if (src.Person.Agency.RequestId.Length >= 30)
@@ -81,7 +92,6 @@ public static SearchRequestEntity ConstructSearchRequestEntity(SearchRequestOrde
}
#endregion
-
if (src.Person.Addresses != null)
{
Address applicantAddress = src.Person.Addresses.FirstOrDefault
(m => m.Owner == OwnerType.Applicant);
@@ -104,6 +114,7 @@ public static SearchRequestEntity ConstructSearchRequestEntity(SearchRequestOrde
{
entity.ApplicantPhoneNumber = src.Person.Phones.FirstOrDefault(m => m.Owner == OwnerType.Applicant)?.PhoneNumber;
}
+
if (src.Person.Identifiers != null)
{
entity.ApplicantSIN = src.Person.Identifiers.FirstOrDefault(
diff --git a/app/DynamicsAdapter/DynamicsAdapter.Web/Mapping/Resolvers.cs b/app/DynamicsAdapter/DynamicsAdapter.Web/Mapping/Resolvers.cs
index 0911cccf..30b42ecf 100644
--- a/app/DynamicsAdapter/DynamicsAdapter.Web/Mapping/Resolvers.cs
+++ b/app/DynamicsAdapter/DynamicsAdapter.Web/Mapping/Resolvers.cs
@@ -180,6 +180,9 @@ public Agency Resolve(SSG_SearchApiRequest source, PersonSearchRequest destinati
ReasonCode = source.SearchRequest?.SearchReason?.ReasonCode switch
{
"EnfPayAgr" => SearchReasonCode.EnfPayAgr,
+ "ApplReasCd1" => SearchReasonCode.ApplReasCd1,
+ "ApplReasCd2" => SearchReasonCode.ApplReasCd2,
+ "ApplReasCd3" => SearchReasonCode.ApplReasCd3,
"ChngAccAgr" => SearchReasonCode.ChngAccAgr,
"ChngCustAg" => SearchReasonCode.ChngCustAg,
"AstRecpAgy" => SearchReasonCode.AstRecpAgy,
diff --git a/app/DynamicsAdapter/DynamicsAdapter.Web/SearchAgency/AgencyRequestController.cs b/app/DynamicsAdapter/DynamicsAdapter.Web/SearchAgency/AgencyRequestController.cs
index e07a5429..55870da3 100644
--- a/app/DynamicsAdapter/DynamicsAdapter.Web/SearchAgency/AgencyRequestController.cs
+++ b/app/DynamicsAdapter/DynamicsAdapter.Web/SearchAgency/AgencyRequestController.cs
@@ -43,58 +43,109 @@ ISearchRequestRegister register
[OpenApiTag("Agency Search Request API")]
public async Task CreateSearchRequest(string requestId, [FromBody]SearchRequestOrdered searchRequestOrdered)
{
- using (LogContext.PushProperty("RequestRef", $"{requestId}"))
- using (LogContext.PushProperty("AgencyCode", $"{searchRequestOrdered?.Person?.Agency?.Code}"))
+ using (LogContext.PushProperty("RequestRef", requestId ?? "(null)"))
+ using (LogContext.PushProperty("AgencyCode", searchRequestOrdered?.Person?.Agency?.Code))
{
- _logger.LogInformation("Get CreateSearchRequest");
- _logger.LogDebug(JsonConvert.SerializeObject(searchRequestOrdered));
- if (string.IsNullOrEmpty(requestId)) return BadRequest(new { ReasonCode = "error", Message = "requestId cannot be empty." });
- if (searchRequestOrdered == null) return BadRequest(new { ReasonCode = "error", Message = "SearchRequestOrdered cannot be empty." });
- if (searchRequestOrdered.Action != RequestAction.NEW) return BadRequest(new { ReasonCode = "error", Message = "CreateSearchRequest should only get NEW request." });
+ _logger.LogInformation("➡️ Start CreateSearchRequest for RequestId: {RequestId}", requestId);
+
+ if (string.IsNullOrEmpty(requestId))
+ {
+ _logger.LogWarning("❌ requestId is missing.");
+ return BadRequest(new { ReasonCode = "error", Message = "requestId cannot be empty." });
+ }
+
+ if (searchRequestOrdered == null)
+ {
+ _logger.LogWarning("❌ SearchRequestOrdered payload is missing.");
+ return BadRequest(new { ReasonCode = "error", Message = "SearchRequestOrdered cannot be empty." });
+ }
+
+ if (searchRequestOrdered.Action != RequestAction.NEW)
+ {
+ _logger.LogWarning(
+ "❌ CreateSearchRequest should only receive NEW action, received: {Action}",
+ searchRequestOrdered.Action);
+
+ return BadRequest(new
+ {
+ ReasonCode = "error",
+ Message = "CreateSearchRequest should only get NEW request."
+ });
+ }
SSG_SearchRequest createdSearchRequest = null;
try
{
+ _logger.LogDebug("Processing SearchRequestOrdered via AgencyRequestService...");
createdSearchRequest = await _agencyRequestService.ProcessSearchRequestOrdered(searchRequestOrdered);
+
if (createdSearchRequest == null)
+ {
+ _logger.LogError("❌ _agencyRequestService returned NULL for ProcessSearchRequestOrdered");
return StatusCode(StatusCodes.Status500InternalServerError);
+ }
- _logger.LogInformation("SearchRequest is created successfully.");
-
+ _logger.LogInformation(
+ "SearchRequest created successfully. SearchRequestId: {SearchRequestId}",
+ createdSearchRequest.SearchRequestId);
}
catch (AgencyRequestException ex)
{
- _logger.LogInformation(ex.Message);
+ _logger.LogError(ex,
+ "❌ AgencyRequestException while creating SearchRequest. Reason: {Reason}",
+ ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, new { ReasonCode = ex.Message, Message = ex.InnerException?.Message });
}
catch (Exception ex)
{
+ _logger.LogError(ex, "❌ Unexpected error while creating SearchRequest.");
+
SSG_SearchRequest createdSR = _agencyRequestService.GetSSGSearchRequest();
if (createdSR != null)
{
+ _logger.LogWarning("Rolling back: cancelling partially created SSG_SearchRequest {SearchRequestId}", createdSR.SearchRequestId);
await _agencyRequestService.SystemCancelSSGSearchRequest(createdSR);
}
- if( ex is Simple.OData.Client.WebRequestException)
+
+ if( ex is Simple.OData.Client.WebRequestException webEx)
{
- _logger.LogError(((Simple.OData.Client.WebRequestException)ex).RequestUri?.AbsoluteUri);
- _logger.LogError(((Simple.OData.Client.WebRequestException)ex).Response);
+ _logger.LogError("OData RequestUri: {Uri}", webEx.RequestUri?.AbsoluteUri);
+ _logger.LogError("OData Response: {Response}", webEx.Response);
}
- _logger.LogError(ex.Message);
+
return StatusCode(StatusCodes.Status500InternalServerError, new { ReasonCode = ex.Message, Message = ex.InnerException?.Message });
}
//try to submit to queue and then get EstimatedDate and positionInQueue
try
{
+ _logger.LogDebug(
+ "Submitting SearchRequest {SearchRequestId} to queue...",
+ createdSearchRequest.SearchRequestId);
+
await _agencyRequestService.SubmitSearchRequestToQueue(createdSearchRequest.SearchRequestId);
createdSearchRequest = await _agencyRequestService.RefreshSearchRequest(createdSearchRequest.SearchRequestId);
- }catch(Exception e)
+
+ _logger.LogInformation(
+ "SearchRequest submitted to queue successfully. SearchRequestId: {SearchRequestId}",
+ createdSearchRequest.SearchRequestId);
+ }catch(Exception ex)
{
- _logger.LogError(e, "submit to queue or get current search request failed.");
+ _logger.LogError(ex,
+ "❌ Failed to submit SearchRequest {SearchRequestId} to queue.",
+ createdSearchRequest.SearchRequestId);
//default value, in case there is error, we still can return accept event.
createdSearchRequest.EstimatedCompletionDate = DateTime.UtcNow.AddMonths(3);
createdSearchRequest.QueuePosition = 9999;
}
+
+ _logger.LogInformation(
+ "Returning SearchRequestSaved event for RequestId {RequestId}",
+ requestId);
+ _logger.LogDebug(
+ "🏁 End CreateSearchRequest for RequestId: {RequestId}",
+ requestId);
+
return Ok(BuildSearchRequestSaved_Create(createdSearchRequest, searchRequestOrdered));
}
}
diff --git a/app/DynamicsAdapter/DynamicsAdapter.Web/SearchAgency/AgencyRequestService.cs b/app/DynamicsAdapter/DynamicsAdapter.Web/SearchAgency/AgencyRequestService.cs
index aefce019..d45a9537 100644
--- a/app/DynamicsAdapter/DynamicsAdapter.Web/SearchAgency/AgencyRequestService.cs
+++ b/app/DynamicsAdapter/DynamicsAdapter.Web/SearchAgency/AgencyRequestService.cs
@@ -61,6 +61,14 @@ public AgencyRequestService(ISearchRequestService searchRequestService, ILogger<
public async Task ProcessSearchRequestOrdered(SearchRequestOrdered searchRequestOrdered)
{
+ _logger.LogDebug("➡️ Start ProcessSearchRequestOrdered for RequestId {RequestId}", searchRequestOrdered.RequestId);
+
+ if (searchRequestOrdered == null)
+ {
+ _logger.LogError("❌ SearchRequestOrdered cannot be null.");
+ throw new ArgumentNullException(nameof(searchRequestOrdered));
+ }
+
_personSought = searchRequestOrdered.Person;
var cts = new CancellationTokenSource();
_cancellationToken = cts.Token;
@@ -68,9 +76,15 @@ public async Task ProcessSearchRequestOrdered(SearchRequestOr
SearchRequestEntity searchRequestEntity = _mapper.Map(searchRequestOrdered);
searchRequestEntity.CreatedByApi = true;
searchRequestEntity.SendNotificationOnCreation = true;
+
_uploadedSearchRequest = await _searchRequestService.CreateSearchRequest(searchRequestEntity, cts.Token);
- if (_uploadedSearchRequest == null) return null;
- _logger.LogInformation("Create Search Request successfully");
+ if (_uploadedSearchRequest == null)
+ {
+ _logger.LogWarning("⚠️ CreateSearchRequest returned null");
+ return null;
+ }
+
+ _logger.LogInformation("Created base Search Request successfully. FileId: {FileId}", _uploadedSearchRequest?.FileId);
PersonEntity personEntity = _mapper.Map(_personSought);
personEntity.SearchRequest = _uploadedSearchRequest;
@@ -78,7 +92,7 @@ public async Task ProcessSearchRequestOrdered(SearchRequestOr
personEntity.IsCreatedByAgency = true;
personEntity.IsPrimary = true;
_uploadedPerson = await _searchRequestService.SavePerson(personEntity, _cancellationToken);
- _logger.LogInformation("Create Person successfully");
+ _logger.LogInformation("Created Person successfully");
await UploadIdentifiers();
await UploadAddresses();
@@ -88,6 +102,9 @@ public async Task ProcessSearchRequestOrdered(SearchRequestOr
await UploadRelatedApplicant(_uploadedSearchRequest.ApplicantFirstName, _uploadedSearchRequest.ApplicantLastName);
await UploadAliases();
await UploadSafetyConcern();
+
+ _logger.LogDebug("🏁 End ProcessSearchRequestOrdered for RequestId {RequestId}", searchRequestOrdered.RequestId);
+
return _uploadedSearchRequest;
}
diff --git a/app/DynamicsAdapter/DynamicsAdapter.Web/searchApi.openapi.json b/app/DynamicsAdapter/DynamicsAdapter.Web/searchApi.openapi.json
index 5c7479be..42255ac4 100644
--- a/app/DynamicsAdapter/DynamicsAdapter.Web/searchApi.openapi.json
+++ b/app/DynamicsAdapter/DynamicsAdapter.Web/searchApi.openapi.json
@@ -497,6 +497,9 @@
"ChngPayAgr",
"EnfPayAgND",
"ChildAbduc",
+ "ApplReasCd1",
+ "ApplReasCd2",
+ "ApplReasCd3",
"AstRecpAgy",
"Unknown",
"Other"
@@ -517,6 +520,9 @@
"ChngPayAgr",
"EnfPayAgND",
"ChildAbduc",
+ "ApplReasCd1",
+ "ApplReasCd2",
+ "ApplReasCd3",
"AstRecpAgy",
"Unknown",
"Other"
@@ -536,7 +542,11 @@
"Carceration",
"DateOfDeath",
"IA",
- "SafetyConcern"
+ "SafetyConcern",
+ "T1taxform",
+ "NoticeofAssessment",
+ "NoticeofReassessment",
+ "FinancialOtherIncome"
],
"enum": [
"Asset",
@@ -549,7 +559,11 @@
"Carceration",
"DateOfDeath",
"IA",
- "SafetyConcern"
+ "SafetyConcern",
+ "T1taxform",
+ "NoticeofAssessment",
+ "NoticeofReassessment",
+ "FinancialOtherIncome"
]
},
"Name": {
diff --git a/app/DynamicsAdapter/Fams3Adapter.Dynamics/Error/DynamicsApiErrorLogger.cs b/app/DynamicsAdapter/Fams3Adapter.Dynamics/Error/DynamicsApiErrorLogger.cs
new file mode 100644
index 00000000..192448e8
--- /dev/null
+++ b/app/DynamicsAdapter/Fams3Adapter.Dynamics/Error/DynamicsApiErrorLogger.cs
@@ -0,0 +1,30 @@
+using Microsoft.Extensions.Logging;
+using Simple.OData.Client;
+using System;
+
+namespace Fams3Adapter.Dynamics.Error
+{
+ public static class DynamicsApiErrorLogger
+ {
+ public static void LogDynamicsError(Exception ex, ILogger logger)
+ {
+ logger.LogError(ex, "❌ Dynamics operation failed");
+
+ var inner = ex.InnerException;
+ while (inner != null)
+ {
+ logger.LogError("⛔ InnerException: {Message}", inner.Message);
+ inner = inner.InnerException;
+ }
+
+ if (ex is WebRequestException webEx && webEx.Response != null)
+ {
+ try
+ {
+ logger.LogError("📝 OData Response Content:\n{Response}", webEx.Response);
+ }
+ catch { }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/DynamicsAdapter/Fams3Adapter.Dynamics/SearchApiRequest/SearchApiRequestService.cs b/app/DynamicsAdapter/Fams3Adapter.Dynamics/SearchApiRequest/SearchApiRequestService.cs
index 421862e2..263d0491 100644
--- a/app/DynamicsAdapter/Fams3Adapter.Dynamics/SearchApiRequest/SearchApiRequestService.cs
+++ b/app/DynamicsAdapter/Fams3Adapter.Dynamics/SearchApiRequest/SearchApiRequestService.cs
@@ -7,7 +7,9 @@
using Fams3Adapter.Dynamics.SearchApiEvent;
using Fams3Adapter.Dynamics.SearchRequest;
using Fams3Adapter.Dynamics.Types;
+using Fams3Adapter.Dynamics.Error;
using Simple.OData.Client;
+using Microsoft.Extensions.Logging;
using Entry = System.Collections.Generic.Dictionary;
@@ -46,11 +48,13 @@ public class SearchApiRequestService : ISearchApiRequestService
{
private readonly IODataClient _oDataClient;
+ private readonly ILogger _logger;
// private readonly SearchApiConfiguration _searchApiConfiguration;
- public SearchApiRequestService(IODataClient oDataClient)
+ public SearchApiRequestService(IODataClient oDataClient, ILogger logger)
{
this._oDataClient = oDataClient;
+ _logger = logger;
}
///
@@ -63,37 +67,52 @@ public async Task> GetAllReadyForSearchAsync(
SSG_DataProvider[] dataProviders,
string availableDataPartners)
{
- List providers = dataProviders.ToList();
- int readyForSearchCode = SearchApiRequestStatusReason.ReadyForSearch.Value;
- List results = new List();
+ try
+ {
+ List providers = dataProviders.ToList();
+ int readyForSearchCode = SearchApiRequestStatusReason.ReadyForSearch.Value;
+ List results = new List();
- //todo: we need to change to use following code, but ODataClient 4 has problems with expand, curent implemented code is a workaround
- //ref: https://powerusers.microsoft.com/t5/Power-Apps-Ideas/Web-API-Implement-expand-on-collections/idi-p/221291
+ _logger.LogDebug("➡️ Start GetAllReadyForSearchAsync...");
- IEnumerable searchApiRequests = await _oDataClient.For()
- .Select(x => x.SearchApiRequestId)
- .Filter(x => x.StatusCode == readyForSearchCode)
- .FindEntriesAsync(cancellationToken);
+ //todo: we need to change to use following code, but ODataClient 4 has problems with expand, curent implemented code is a workaround
+ //ref: https://powerusers.microsoft.com/t5/Power-Apps-Ideas/Web-API-Implement-expand-on-collections/idi-p/221291
- foreach (SSG_SearchApiRequest request in searchApiRequests)
- {
- SSG_SearchApiRequest searchApiRequest = await _oDataClient.For()
- .Key(request.SearchApiRequestId)
- .Expand(x => x.Identifiers)
- .Expand(x => x.DataProviders)
- .Expand(x => x.SearchRequest)
- .FindEntryAsync(cancellationToken);
- if (searchApiRequest.SearchRequest != null)
+ IEnumerable searchApiRequests = await _oDataClient.For()
+ .Select(x => x.SearchApiRequestId)
+ .Filter(x => x.StatusCode == readyForSearchCode)
+ .FindEntriesAsync(cancellationToken);
+
+ foreach (SSG_SearchApiRequest request in searchApiRequests)
{
- searchApiRequest.SearchRequest = await _oDataClient.For()
- .Key(searchApiRequest.SearchRequest.SearchRequestId)
- .Expand(x => x.SearchReason).FindEntryAsync(cancellationToken);
+ SSG_SearchApiRequest searchApiRequest = await _oDataClient.For()
+ .Key(request.SearchApiRequestId)
+ .Expand(x => x.Identifiers)
+ .Expand(x => x.DataProviders)
+ .Expand(x => x.SearchRequest)
+ .FindEntryAsync(cancellationToken);
+ if (searchApiRequest.SearchRequest != null)
+ {
+ searchApiRequest.SearchRequest = await _oDataClient.For()
+ .Key(searchApiRequest.SearchRequest.SearchRequestId)
+ .Expand(x => x.SearchReason).FindEntryAsync(cancellationToken);
+ }
+ searchApiRequest.IsFailed = false;
+ UpdateProviderInfo(providers, searchApiRequest, availableDataPartners);
+ results.Add(searchApiRequest);
}
- searchApiRequest.IsFailed = false;
- UpdateProviderInfo(providers, searchApiRequest, availableDataPartners);
- results.Add(searchApiRequest);
+ _logger.LogDebug("🏁 End GetAllReadyForSearchAsync with {Count} results.", results.Count);
+ return results;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex,
+ "❌ Failed to retrieve SearchApiRequests ready for search. AvailableDataPartners: {Partners}",
+ availableDataPartners);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
+ // rethrow to preserve existing behavior
+ throw;
}
- return results;
}
private static void UpdateProviderInfo(List providers, SSG_SearchApiRequest searchApiRequest, string availableDataPartners)
@@ -137,15 +156,37 @@ private static void UpdateProviderInfo(List providers, SSG_Sea
public async Task GetLinkedSearchRequestIdAsync(Guid searchApiRequestId,
CancellationToken cancellationToken)
{
- if (searchApiRequestId == default || searchApiRequestId == Guid.Empty) throw new ArgumentNullException(nameof(searchApiRequestId));
+ if (searchApiRequestId == default || searchApiRequestId == Guid.Empty)
+ {
+ throw new ArgumentNullException(nameof(searchApiRequestId));
+ }
- var result = await _oDataClient
- .For()
- .Key(searchApiRequestId)
- .Select(x => x.SearchRequestId)
- .FindEntryAsync(cancellationToken);
+ _logger.LogDebug("➡️ Start GetLinkedSearchRequestIdAsync for SearchApiRequestId {Id}", searchApiRequestId);
- return result.SearchRequestId;
+ try
+ {
+ SSG_SearchApiRequest result = await _oDataClient
+ .For()
+ .Key(searchApiRequestId)
+ .Select(x => x.SearchRequestId)
+ .FindEntryAsync(cancellationToken);
+
+ _logger.LogDebug(
+ "🏁 End GetLinkedSearchRequestIdAsync for SearchApiRequestId {Id} → SearchRequestId {SearchRequestId}",
+ searchApiRequestId,
+ result?.SearchRequestId);
+
+ return result.SearchRequestId;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex,
+ "❌ Failed to retrieve linked SearchRequestId for SearchApiRequestId {Id}",
+ searchApiRequestId);
+
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
+ throw; // Re-throw so calling code can handle it
+ }
}
///
@@ -156,45 +197,162 @@ public async Task GetLinkedSearchRequestIdAsync(Guid searchApiRequestId,
///
public async Task MarkInProgress(Guid searchApiRequestId, CancellationToken cancellationToken)
{
- if (searchApiRequestId == default || searchApiRequestId == Guid.Empty) throw new ArgumentNullException(nameof(searchApiRequestId));
+ if (searchApiRequestId == default || searchApiRequestId == Guid.Empty)
+ {
+ throw new ArgumentNullException(nameof(searchApiRequestId));
+ }
+ try
+ {
+ _logger.LogDebug("➡️ Start MarkInProgress for SearchApiRequestId {Id}", searchApiRequestId);
- return await _oDataClient
- .For()
- .Key(searchApiRequestId)
- .Set(new Entry { { Keys.DYNAMICS_STATUS_CODE_FIELD, SearchApiRequestStatusReason.InProgress.Value } })
- .UpdateEntryAsync(cancellationToken);
- }
+ SSG_SearchApiRequest updatedRequest = await _oDataClient
+ .For()
+ .Key(searchApiRequestId)
+ .Set(new Entry
+ {
+ { Keys.DYNAMICS_STATUS_CODE_FIELD, SearchApiRequestStatusReason.InProgress.Value }
+ })
+ .UpdateEntryAsync(cancellationToken);
+
+ _logger.LogDebug(
+ "🏁 End MarkInProgress for SearchApiRequestId {Id} → StatusCode {StatusCode}",
+ searchApiRequestId,
+ updatedRequest?.StatusCode);
+ return updatedRequest;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex,
+ "❌ Failed to mark SearchApiRequestId {Id} as InProgress",
+ searchApiRequestId);
- public async Task AddEventAsync(Guid searchApiRequestId, SSG_SearchApiEvent searchApiEvent,
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
+
+ throw; // rethrow for upstream handling
+ }
+ }
+
+ public async Task AddEventAsync(
+ Guid searchApiRequestId,
+ SSG_SearchApiEvent searchApiEvent,
CancellationToken cancellationToken)
{
- if (searchApiRequestId == default || searchApiRequestId == Guid.Empty) throw new ArgumentNullException(nameof(searchApiRequestId));
+ if (searchApiRequestId == default || searchApiRequestId == Guid.Empty)
+ {
+ throw new ArgumentNullException(nameof(searchApiRequestId));
+ }
- searchApiEvent.SearchApiRequest = new SSG_SearchApiRequest() { SearchApiRequestId = searchApiRequestId};
+ try
+ {
+ _logger.LogDebug(
+ "➡️ Start AddEventAsync for SearchApiRequestId {Id} with EventType {EventType}",
+ searchApiRequestId,
+ searchApiEvent?.EventType);
- return await this._oDataClient.For().Set(searchApiEvent).InsertEntryAsync(cancellationToken);
+ searchApiEvent.SearchApiRequest = new SSG_SearchApiRequest
+ {
+ SearchApiRequestId = searchApiRequestId
+ };
+
+ SSG_SearchApiEvent insertedEvent = await _oDataClient
+ .For()
+ .Set(searchApiEvent)
+ .InsertEntryAsync(cancellationToken);
+
+ _logger.LogDebug(
+ "🏁 End AddEventAsync for SearchApiRequestId {Id} → EventId {EventId}",
+ searchApiRequestId,
+ insertedEvent?.Id);
+
+ return insertedEvent;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex,
+ "❌ Failed to add event for SearchApiRequestId {Id} with EventType {EventType}",
+ searchApiRequestId,
+ searchApiEvent?.EventType);
+
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
+ throw; // rethrow to allow upstream handling
+ }
}
- public async Task> GetEventsAsync(Guid searchApiRequestId, CancellationToken cancellationToken)
+ public async Task> GetEventsAsync(
+ Guid searchApiRequestId,
+ CancellationToken cancellationToken)
{
- if (searchApiRequestId == default || searchApiRequestId == Guid.Empty) throw new ArgumentNullException(nameof(searchApiRequestId));
- IEnumerable events = await this._oDataClient.For()
- .Filter(m=>m.SearchApiRequest.SearchApiRequestId==searchApiRequestId)
- .FindEntriesAsync(cancellationToken);
+ if (searchApiRequestId == default || searchApiRequestId == Guid.Empty)
+ {
+ throw new ArgumentNullException(nameof(searchApiRequestId));
+ }
- return events;
+ try
+ {
+ _logger.LogDebug("➡️ Start GetEventsAsync for SearchApiRequestId {Id}", searchApiRequestId);
+
+ IEnumerable events = await _oDataClient
+ .For()
+ .Filter(m => m.SearchApiRequest.SearchApiRequestId == searchApiRequestId)
+ .FindEntriesAsync(cancellationToken);
+
+ _logger.LogDebug(
+ "🏁 End GetEventsAsync for SearchApiRequestId {Id} → {Count} events retrieved",
+ searchApiRequestId,
+ events?.Count() ?? 0);
+
+ return events;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex,
+ "❌ Failed to retrieve events for SearchApiRequestId {Id}",
+ searchApiRequestId);
+
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
+ throw; // rethrow so upstream code can handle
+ }
}
- public async Task MarkComplete(Guid searchApiRequestId, CancellationToken cancellationToken)
+ public async Task MarkComplete(
+ Guid searchApiRequestId,
+ CancellationToken cancellationToken)
{
- if (searchApiRequestId == default || searchApiRequestId == Guid.Empty) throw new ArgumentNullException(nameof(searchApiRequestId));
+ if (searchApiRequestId == default || searchApiRequestId == Guid.Empty)
+ {
+ throw new ArgumentNullException(nameof(searchApiRequestId));
+ }
+
+ try
+ {
+ _logger.LogDebug("➡️ Start MarkComplete for SearchApiRequestId {Id}", searchApiRequestId);
- return await _oDataClient
- .For()
- .Key(searchApiRequestId)
- .Set(new Entry { { Keys.DYNAMICS_STATUS_CODE_FIELD, SearchApiRequestStatusReason.Complete.Value } })
- .UpdateEntryAsync(cancellationToken);
+ SSG_SearchApiRequest updatedRequest = await _oDataClient
+ .For()
+ .Key(searchApiRequestId)
+ .Set(new Entry
+ {
+ { Keys.DYNAMICS_STATUS_CODE_FIELD, SearchApiRequestStatusReason.Complete.Value }
+ })
+ .UpdateEntryAsync(cancellationToken);
+
+ _logger.LogDebug(
+ "🏁 End MarkComplete for SearchApiRequestId {Id} → StatusCode {StatusCode}",
+ searchApiRequestId,
+ updatedRequest?.StatusCode);
+
+ return updatedRequest;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex,
+ "❌ Failed to mark SearchApiRequestId {Id} as Complete",
+ searchApiRequestId);
+
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
+ throw; // rethrow for upstream handling
+ }
}
///
@@ -202,52 +360,82 @@ public async Task MarkComplete(Guid searchApiRequestId, Ca
///
///
///
- public async Task> GetAllValidFailedSearchRequest(CancellationToken cancellationToken, SSG_DataProvider[] dataProviders)
+ public async Task> GetAllValidFailedSearchRequest(
+ CancellationToken cancellationToken,
+ SSG_DataProvider[] dataProviders)
{
-
-
List results = new List();
//todo: we need to change to use following code, but ODataClient 4 has problems with expand, curent implemented code is a workaround
//ref: https://powerusers.microsoft.com/t5/Power-Apps-Ideas/Web-API-Implement-expand-on-collections/idi-p/221291
- foreach (SSG_DataProvider provider in dataProviders)
+ try
{
- string adaptorName = provider.AdaptorName;
- int noOfDaysToRetry = provider.NumberOfDaysToRetry;
- int allRetryDone = NullableBooleanType.No.Value;
- IEnumerable searchApiRequests = await _oDataClient.For()
- .Select(x => x.SearchAPIRequestId)
- .Filter(x => x.NumberOfFailures > 0)
- .Filter(x => x.AdaptorName == adaptorName)
- .Filter(x => x.NumberOfFailures < noOfDaysToRetry)
- .Filter(x => x.AllRetriesDone == allRetryDone)
- .FindEntriesAsync(cancellationToken);
+ _logger.LogDebug("➡️ Start GetAllValidFailedSearchRequest for {ProviderCount} providers", dataProviders?.Length ?? 0);
- foreach (SSG_SearchapiRequestDataProvider request in searchApiRequests)
+ foreach (SSG_DataProvider provider in dataProviders)
{
+ string adaptorName = provider.AdaptorName;
+ int noOfDaysToRetry = provider.NumberOfDaysToRetry;
+ int allRetryDone = NullableBooleanType.No.Value;
+ IEnumerable searchApiRequests = await _oDataClient.For()
+ .Select(x => x.SearchAPIRequestId)
+ .Filter(x => x.NumberOfFailures > 0)
+ .Filter(x => x.AdaptorName == adaptorName)
+ .Filter(x => x.NumberOfFailures < noOfDaysToRetry)
+ .Filter(x => x.AllRetriesDone == allRetryDone)
+ .FindEntriesAsync(cancellationToken);
+
+ foreach (SSG_SearchapiRequestDataProvider request in searchApiRequests)
+ {
- SSG_SearchApiRequest searchApiRequest = await _oDataClient.For()
- .Key(request.SearchAPIRequestId)
- .Expand(x => x.Identifiers)
- .Expand(x => x.DataProviders)
- .Expand(x => x.SearchRequest)
- .FindEntryAsync(cancellationToken);
- FilterAffectedDataProvider( provider, searchApiRequest);
- searchApiRequest.IsFailed = true;
- results.Add(searchApiRequest);
-
-
-
+ SSG_SearchApiRequest searchApiRequest = await _oDataClient.For()
+ .Key(request.SearchAPIRequestId)
+ .Expand(x => x.Identifiers)
+ .Expand(x => x.DataProviders)
+ .Expand(x => x.SearchRequest)
+ .FindEntryAsync(cancellationToken);
+ FilterAffectedDataProvider( provider, searchApiRequest);
+ searchApiRequest.IsFailed = true;
+ results.Add(searchApiRequest);
+ }
}
+ _logger.LogDebug(
+ "🏁 End GetAllValidFailedSearchRequest → {Count} failed requests retrieved",
+ results.Count);
+
+ return results;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "❌ Failed to retrieve valid failed search requests");
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
+ throw;
}
- return results;
}
public async Task> GetDataProvidersList(CancellationToken cancellationToken)
{
- return await _oDataClient.For()
- .FindEntriesAsync(cancellationToken);
+ try
+ {
+ _logger.LogDebug("➡️ Start GetDataProvidersList");
+
+ IEnumerable dataProviders = await _oDataClient
+ .For()
+ .FindEntriesAsync(cancellationToken);
+
+ _logger.LogDebug(
+ "🏁 End GetDataProvidersList → {Count} data providers retrieved",
+ dataProviders?.Count() ?? 0);
+
+ return dataProviders;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "❌ Failed to retrieve data providers list");
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
+ throw;
+ }
}
private static void FilterAffectedDataProvider(SSG_DataProvider provider, SSG_SearchApiRequest searchApiRequest)
@@ -261,23 +449,46 @@ private static void FilterAffectedDataProvider(SSG_DataProvider provider, SSG_Se
searchApiRequest.DataProviders = list.ToArray();
}
-
public async Task GetSearchApiRequest(string searchRequestKey, CancellationToken cancellationToken)
{
- if (string.IsNullOrEmpty(searchRequestKey)) throw new ArgumentNullException(nameof(searchRequestKey));
- string[] strs = searchRequestKey.Split("_");
- if (strs.Length > 1)
+ if (string.IsNullOrEmpty(searchRequestKey))
{
- string sequence = strs[1];
- IEnumerable searchApiRequests = await _oDataClient.For()
- .Select(x => x.SearchApiRequestId)
- .Filter(x => x.SequenceNumber == sequence)
- .FindEntriesAsync(cancellationToken);
+ throw new ArgumentNullException(nameof(searchRequestKey));
+ }
+
+ try
+ {
+ _logger.LogDebug("➡️ Start GetSearchApiRequest for SearchRequestKey: {Key}", searchRequestKey);
+
+ string[] parts = searchRequestKey.Split("_");
+ SSG_SearchApiRequest result = null;
+
+ if (parts.Length > 1)
+ {
+ string sequence = parts[1];
+
+ IEnumerable searchApiRequests = await _oDataClient
+ .For()
+ .Select(x => x.SearchApiRequestId)
+ .Filter(x => x.SequenceNumber == sequence)
+ .FindEntriesAsync(cancellationToken);
+
+ result = searchApiRequests?.FirstOrDefault();
+ }
- return searchApiRequests?.FirstOrDefault();
+ _logger.LogDebug(
+ "🏁 End GetSearchApiRequest → Found: {Found}, SearchRequestKey: {Key}",
+ result != null,
+ searchRequestKey);
+
+ return result;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "❌ Failed to retrieve SearchApiRequest for key {Key}", searchRequestKey);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
+ throw;
}
-
- return null;
}
}
}
\ No newline at end of file
diff --git a/app/DynamicsAdapter/Fams3Adapter.Dynamics/SearchRequest/SSG_SearchRequest.cs b/app/DynamicsAdapter/Fams3Adapter.Dynamics/SearchRequest/SSG_SearchRequest.cs
index 00d2e23b..2455803f 100644
--- a/app/DynamicsAdapter/Fams3Adapter.Dynamics/SearchRequest/SSG_SearchRequest.cs
+++ b/app/DynamicsAdapter/Fams3Adapter.Dynamics/SearchRequest/SSG_SearchRequest.cs
@@ -127,6 +127,22 @@ public class SearchRequestEntity
[DisplayName("Info Requested Date of Death")]
public bool DateOfDeathRequested { get; set; }
+ [JsonProperty("ssg_inforequestedtaxincomeinformation")]
+ [DisplayName("Info Requested T1 tax info")]
+ public bool T1taxform { get; set; }
+
+ [JsonProperty("fams_inforequestednoticeofassessment")]
+ [DisplayName("Info Requested Notice of Assessment")]
+ public bool NoticeofAssessment { get; set; }
+
+ [JsonProperty("fams_inforequestednoticeofreassessment")]
+ [DisplayName("Info Requested Notice of Reassessment")]
+ public bool NoticeofReassessment { get; set; }
+
+ [JsonProperty("fams_inforequestedfinancialotherincome")]
+ [DisplayName("Info Requested Financial Other Income")]
+ public bool FinancialOtherIncome { get; set; }
+
[JsonProperty("ssg_inforequesteddriverslicense")]
[DisplayName("Info Requested Driver's License")]
public bool DriverLicenseRequested { get; set; }
diff --git a/app/DynamicsAdapter/Fams3Adapter.Dynamics/SearchRequest/SearchRequestService.cs b/app/DynamicsAdapter/Fams3Adapter.Dynamics/SearchRequest/SearchRequestService.cs
index 9a0da127..1c72e651 100644
--- a/app/DynamicsAdapter/Fams3Adapter.Dynamics/SearchRequest/SearchRequestService.cs
+++ b/app/DynamicsAdapter/Fams3Adapter.Dynamics/SearchRequest/SearchRequestService.cs
@@ -20,6 +20,7 @@
using Fams3Adapter.Dynamics.ResultTransaction;
using Fams3Adapter.Dynamics.SafetyConcern;
using Fams3Adapter.Dynamics.Vehicle;
+using Fams3Adapter.Dynamics.Error;
using Microsoft.Extensions.Logging;
using Simple.OData.Client;
using System;
@@ -125,7 +126,7 @@ public async Task CreateIdentifier(IdentifierEntity identifier,
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw; // rethrow so caller can react
}
@@ -175,7 +176,7 @@ public async Task SavePerson(PersonEntity person, CancellationToken
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
}
@@ -199,7 +200,7 @@ public async Task CreatePhoneNumber(PhoneNumberEntity phone, Ca
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
@@ -229,7 +230,7 @@ public async Task CreateEmail(EmailEntity email, CancellationToken ca
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
@@ -255,7 +256,7 @@ public async Task CreateTransaction(
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
@@ -312,7 +313,7 @@ public async Task CreateAddress(AddressEntity address, Cancellation
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
@@ -350,7 +351,7 @@ public async Task CreateTaxIncomeInformation(TaxIncome
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw; // allow caller to see failure too
}
@@ -400,7 +401,7 @@ public async Task CreateFinancialOtherIncome(Financia
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw; // allow caller to see failure too
}
@@ -411,29 +412,6 @@ public async Task CreateFinancialOtherIncome(Financia
return insertedRecord;
}
- private void LogDynamicsError(Exception ex)
- {
- _logger.LogError(ex, "❌ Dynamics insert failed");
-
- var error = ex.InnerException;
- while (error != null)
- {
- _logger.LogError("⛔ InnerException: {Message}", error.Message);
- error = error.InnerException;
- }
-
- // Attempt to extract OData error JSON if present
- if (ex is WebRequestException webEx && webEx.Response != null)
- {
- try
- {
- var body = webEx.Response;
- _logger.LogError("📝 OData Response Content:\n{Response}", body);
- }
- catch { }
- }
- }
-
private IEnumerable _taxCodes { get; set; }
public async Task> GetTaxCodes(CancellationToken cancellationToken)
{
@@ -461,7 +439,7 @@ public async Task CreateName(AliasEntity name, CancellationToken can
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
@@ -496,7 +474,7 @@ public async Task CreateEmployment(EmploymentEntity employment,
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
@@ -522,7 +500,7 @@ public async Task CreateEmploymentContact(EmploymentConta
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
@@ -548,7 +526,7 @@ public async Task CreateRelatedPerson(RelatedPersonEntity relatedP
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
@@ -574,7 +552,7 @@ public async Task CreateBankInfo(BankingInformatio
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
@@ -601,7 +579,7 @@ public async Task CreateVehicle(VehicleEntity vehicle, Cancel
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
@@ -619,7 +597,7 @@ public async Task CreateVehicle(VehicleEntity vehicle, Cancel
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
@@ -645,7 +623,7 @@ public async Task CreateAssetOwner(AssetOwnerEntity owner, Cance
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
@@ -671,7 +649,7 @@ public async Task CreateOtherAsset(AssetOtherEntity otherAsset,
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
@@ -696,7 +674,7 @@ private async Task GetDuplicatedCompensation(Compensa
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
@@ -718,7 +696,7 @@ private async Task GetDuplicatedCompensation(Compensa
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
}
@@ -753,7 +731,7 @@ public async Task CreateCompensationClaim(Compensatio
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
}
@@ -789,7 +767,7 @@ public async Task CreateInsuranceClaim(ICBCClaimEntity insu
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
}
@@ -803,7 +781,7 @@ public async Task CreateInsuranceClaim(ICBCClaimEntity insu
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
}
@@ -825,7 +803,7 @@ public async Task CreateSimplePhoneNumber(SimplePhoneNumb
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
}
@@ -847,23 +825,33 @@ public async Task CreateInvolvedParty(InvolvedPartyEntity inv
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
}
public async Task CreateSearchRequest(SearchRequestEntity searchRequest, CancellationToken cancellationToken)
{
+ _logger.LogDebug("➡️ Start base CreateSearchRequest");
+
SearchRequestEntity linkedSearchRequest = await LinkSearchRequestRef(searchRequest, cancellationToken);
+
try
{
- return await this._oDataClient.For()
+ var result = await this._oDataClient.For()
.Set(linkedSearchRequest)
.InsertEntryAsync(cancellationToken);
+
+ _logger.LogInformation("🏁 End Dynamics base SearchRequest created successfully. SearchRequestId: {SearchRequestId}, FileId: {FileId}",
+ result?.SearchRequestId,
+ result?.FileId);
+
+ return result;
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ _logger.LogError(ex, "❌ Failed to create Dynamics SearchRequest.");
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
}
@@ -885,7 +873,7 @@ public async Task CreateSafetyConcern(SafetyConcernEnti
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
}
@@ -918,7 +906,7 @@ public async Task CancelSearchRequest(string fileId, string c
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
}
@@ -942,7 +930,7 @@ public async Task GetCurrentSearchRequest(Guid searchRequestI
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
}
@@ -973,7 +961,7 @@ public async Task SystemCancelSearchRequest(SSG_SearchRequest
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
}
@@ -1000,7 +988,7 @@ await _oDataClient
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
_logger.LogError(ex, "Failed to delete SearchRequest with FileId={FileId}", fileId);
throw;
}
@@ -1053,7 +1041,7 @@ public async Task GetSearchRequest(string fileId, Cancellatio
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
_logger.LogError(ex, "Error retrieving SearchRequest with FileId={FileId}", fileId);
throw;
}
@@ -1083,7 +1071,7 @@ public async Task GetPerson(Guid personId, CancellationToken cancell
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
}
@@ -1107,7 +1095,7 @@ public async Task GetEmployment(Guid employmentId, CancellationT
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
}
@@ -1125,7 +1113,7 @@ public async Task UpdateSearchRequest(Guid requestId, IDictio
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
}
@@ -1150,7 +1138,7 @@ public async Task UpdatePerson(SSG_Person existedPerson, IDictionary
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
}
@@ -1168,7 +1156,7 @@ public async Task UpdateRelatedPerson(Guid relatedPersonId, IDicti
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
}
@@ -1185,7 +1173,7 @@ public async Task UpdateEmployment(Guid employmentId, IDictionar
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
}
@@ -1202,7 +1190,7 @@ public async Task UpdateIdentifier(Guid identifierId, IDictionar
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
}
@@ -1220,7 +1208,7 @@ public async Task UpdateSafetyConcern(Guid safetyId, ID
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
}
@@ -1242,7 +1230,7 @@ public async Task CreateNotes(NotesEntity note, CancellationToken ca
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
}
@@ -1260,7 +1248,7 @@ private async Task LinkSearchRequestRef(SearchRequestEntity
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
@@ -1280,15 +1268,46 @@ private async Task LinkSearchRequestRef(SearchRequestEntity
public async Task GetSearchReason(string reasonCode, CancellationToken cancellationToken)
{
//find reasoncode
+ if (string.IsNullOrEmpty(reasonCode))
+ {
+ throw new ArgumentNullException(nameof(reasonCode));
+ }
+
+ _logger.LogDebug(
+ "➡️ Start GetSearchReason for ReasonCode: {ReasonCode}",
+ reasonCode);
+
try
{
- return await _oDataClient.For()
- .Filter(x => x.ReasonCode == reasonCode)
- .FindEntryAsync(cancellationToken);
+ SSG_SearchRequestReason result = await _oDataClient
+ .For()
+ .Filter(x => x.ReasonCode == reasonCode)
+ .FindEntryAsync(cancellationToken);
+
+ if (result == null)
+ {
+ _logger.LogWarning(
+ "⚠️ No SearchRequestReason found for ReasonCode: {ReasonCode}",
+ reasonCode);
+ }
+ else
+ {
+ _logger.LogDebug(
+ "Found SearchRequestReason for ReasonCode: {ReasonCode}: {@Result}",
+ reasonCode,
+ result);
+ }
+
+ _logger.LogDebug("🏁 End GetSearchReason for ReasonCode: {ReasonCode}", reasonCode);
+ return result;
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ _logger.LogError(ex,
+ "❌ Error while retrieving SearchRequestReason for ReasonCode {ReasonCode}",
+ reasonCode);
+
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
}
@@ -1319,7 +1338,7 @@ public async Task GetSearchAgencyLocation(string locationCod
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
return null;
}
}
@@ -1335,7 +1354,7 @@ public async Task GetEmploymentCountry(string countryText, Cancella
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
}
@@ -1351,7 +1370,7 @@ public async Task GetEmploymentSubdivision(string subDiv
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
}
@@ -1369,7 +1388,7 @@ await _oDataClient
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
}
@@ -1394,7 +1413,7 @@ await _oDataClient
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
}
@@ -1419,7 +1438,7 @@ public async Task> GetAutoCloseSearchRequestAsync
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
}
@@ -1450,7 +1469,7 @@ private async Task FindDuplicatedPerson(PersonEntity newPerson)
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
}
@@ -1469,7 +1488,7 @@ await _oDataClient
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
}
@@ -1490,7 +1509,7 @@ public async Task UpdateSearchRequestStatusAutoClosed(Guid se
}
catch (Exception ex)
{
- LogDynamicsError(ex);
+ DynamicsApiErrorLogger.LogDynamicsError(ex, _logger);
throw;
}
}
diff --git a/app/SearchApi/BcGov.Fams3.SearchApi.Contracts/BcGov.Fams3.SearchApi.Contracts.csproj b/app/SearchApi/BcGov.Fams3.SearchApi.Contracts/BcGov.Fams3.SearchApi.Contracts.csproj
index b7f67c29..3789bf0a 100644
--- a/app/SearchApi/BcGov.Fams3.SearchApi.Contracts/BcGov.Fams3.SearchApi.Contracts.csproj
+++ b/app/SearchApi/BcGov.Fams3.SearchApi.Contracts/BcGov.Fams3.SearchApi.Contracts.csproj
@@ -2,11 +2,11 @@
net8.0
- 1.1.2
+ 1.1.3
PathFinder
BcGov
- 1.1.2
+ 1.1.3
True
false
README.md
diff --git a/app/SearchApi/BcGov.Fams3.SearchApi.Contracts/Person/Agency.cs b/app/SearchApi/BcGov.Fams3.SearchApi.Contracts/Person/Agency.cs
index fa97eecd..0e77960a 100644
--- a/app/SearchApi/BcGov.Fams3.SearchApi.Contracts/Person/Agency.cs
+++ b/app/SearchApi/BcGov.Fams3.SearchApi.Contracts/Person/Agency.cs
@@ -88,6 +88,9 @@ public enum SearchReasonCode
ChngPayAgr,
EnfPayAgND,
ChildAbduc,
+ ApplReasCd1,
+ ApplReasCd2,
+ ApplReasCd3,
AstRecpAgy,
Unknown,
Other,
@@ -105,6 +108,10 @@ public enum InformationRequested
Carceration,
DateOfDeath,
IA,
- SafetyConcern
+ SafetyConcern,
+ T1taxform,
+ NoticeofAssessment,
+ NoticeofReassessment,
+ FinancialOtherIncome
}
}
diff --git a/app/SearchApi/SearchRequest.Adaptor/Notifier/SearchRequestNotifier.cs b/app/SearchApi/SearchRequest.Adaptor/Notifier/SearchRequestNotifier.cs
index 3045f937..29e1fab8 100644
--- a/app/SearchApi/SearchRequest.Adaptor/Notifier/SearchRequestNotifier.cs
+++ b/app/SearchApi/SearchRequest.Adaptor/Notifier/SearchRequestNotifier.cs
@@ -61,9 +61,13 @@ private async Task NotifySearchRequestOrderedEvent(
int retryTimes,
int maxRetryTimes)
{
- var webHookName = "SearchRequest";
+ if (searchRequestOrdered == null)
+ {
+ throw new ArgumentNullException(nameof(searchRequestOrdered));
+ }
- if (searchRequestOrdered == null) throw new ArgumentNullException(nameof(SearchRequestOrdered));
+ const string webHookName = "SearchRequest";
+ _logger.LogDebug("➡️ Start NotifySearchRequestOrderedEvent for RequestId: {RequestId} and WebHookName: {webHookName}", requestId, webHookName);
string eventName = searchRequestOrdered.Action switch
{
@@ -76,29 +80,29 @@ private async Task NotifySearchRequestOrderedEvent(
foreach (var webHook in _searchRequestOptions.WebHooks)
{
_logger.LogDebug(
- $"The webHook {webHookName} notification is attempting to send {eventName} for {webHook.Name} webhook.");
+ $"The webHook {webHookName} notification is attempting to send {eventName} for {webHook.Name} webhook.");
if (!URLHelper.TryCreateUri(webHook.Uri, eventName, $"{requestId}", out var endpoint))
{
- _logger.LogWarning(
- $"The webHook {webHookName} notification uri is not established or is not an absolute Uri for {webHook.Name}. Set the WebHook.Uri value on SearchApi.WebHooks settings.");
+ _logger.LogError(
+ "❌ Invalid webhook URI. WebHookName: {WebHookName}, ConfigName: {Name}. Please verify SearchRequestAdaptor.WebHooks settings.",
+ webHookName,
+ webHook.Name);
throw new Exception($"The webHook {webHookName} notification uri is not established or is not an absolute Uri for {webHook.Name}.");
}
- using var request = new HttpRequestMessage();
-
try
{
- StringContent content = new StringContent(JsonConvert.SerializeObject(searchRequestOrdered));
+ using var request = new HttpRequestMessage(HttpMethod.Post, endpoint);
+ var json = JsonConvert.SerializeObject(searchRequestOrdered);
- content.Headers.ContentType =
+ request.Content = new StringContent(json);
+ request.Content.Headers.ContentType =
System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json");
- request.Content = content;
- request.Method = HttpMethod.Post;
request.Headers.Accept.Add(
System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
request.Headers.Add("X-ApiKey", _searchRequestOptions.ApiKeyForDynadaptor);
- request.RequestUri = endpoint;
+
var response = await _httpClient.SendAsync(request, cancellationToken);
if (!response.IsSuccessStatusCode)
@@ -108,7 +112,7 @@ private async Task NotifySearchRequestOrderedEvent(
string reason = await response.Content.ReadAsStringAsync();
_logger.LogError(
$"The webHook {webHookName} notification has not executed status {eventName} successfully for {webHook.Name} webHook. The error code is {response.StatusCode.GetHashCode()}.Reason is {reason}.");
- throw(new Exception($"The webHook {webHookName} notification has not executed status {eventName} successfully for {webHook.Name} webHook. The error code is {response.StatusCode.GetHashCode()}."));
+ throw new Exception($"The webHook {webHookName} notification has not executed status {eventName} successfully for {webHook.Name} webHook. The error code is {response.StatusCode.GetHashCode()}.");
}
else if (response.StatusCode == System.Net.HttpStatusCode.BadRequest)
{
@@ -186,62 +190,99 @@ await _searchRequestEventPublisher.PublishSearchRequestRejected(
}
catch (Exception exception)
{
- _logger.LogError(exception, exception.Message);
+ _logger.LogError(exception, $"Webhook {webHook.Name} failed: {exception.Message}");
throw;
}
}
+ _logger.LogDebug("🏁 End NotifySearchRequestOrderedEvent for RequestId: {RequestId}", requestId);
}
- private async Task NotifyNotificationAcknowledged(string requestId, NotificationAcknowledged notificationAck,CancellationToken cancellationToken)
+ private async Task NotifyNotificationAcknowledged(
+ string requestId,
+ NotificationAcknowledged notificationAck,
+ CancellationToken cancellationToken)
{
- var webHookName = "SearchRequest";
+ if (string.IsNullOrEmpty(requestId))
+ {
+ throw new ArgumentNullException(nameof(requestId));
+ }
+
+ if (notificationAck == null)
+ {
+ throw new ArgumentNullException(nameof(notificationAck));
+ }
- if (notificationAck == null) throw new ArgumentNullException(nameof(NotificationAcknowledged));
+ const string webHookName = "SearchRequest";
+ _logger.LogDebug("➡️ Starting NotifyNotificationAcknowledged for RequestId: {RequestId} and WebHookName: {webHookName}", requestId, webHookName);
foreach (var webHook in _searchRequestOptions.WebHooks)
{
if (!URLHelper.TryCreateUri(webHook.Uri, "NotificationAcknowledged", $"{requestId}", out var endpoint))
{
_logger.LogError(
- $"The webHook {webHookName} notification uri is not established or is not an absolute Uri for {webHook.Name}. Set the WebHook.Uri value on SearchApi.WebHooks settings.");
+ "❌ Invalid webhook URI for RequestId {RequestId}. WebHookName: {WebHookName}, ConfigName: {Name}. Please verify SearchRequestAdaptor.WebHooks settings.",
+ requestId,
+ webHookName,
+ webHook.Name);
return;
}
- using var request = new HttpRequestMessage();
+ using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, endpoint);
try
{
- StringContent content = new StringContent(JsonConvert.SerializeObject(notificationAck));
-
+ string payload = JsonConvert.SerializeObject(notificationAck);
+ StringContent content = new StringContent(payload);
content.Headers.ContentType =
System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json");
- request.Content = content;
- request.Method = HttpMethod.Post;
request.Headers.Accept.Add(
System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
request.Headers.Add("X-ApiKey", _searchRequestOptions.ApiKeyForDynadaptor);
- request.RequestUri = endpoint;
- var response = await _httpClient.SendAsync(request, cancellationToken);
+ request.Content = content;
+
+ _logger.LogDebug("Posting notification to webhook: {Endpoint}", endpoint);
+ _logger.LogDebug(
+ "📨 Posting WebHook notification for RequestId {RequestId} to {Endpoint}. PayloadSize={PayloadSize} bytes",
+ requestId,
+ endpoint,
+ payload.Length);
+ HttpResponseMessage response = await _httpClient.SendAsync(request, cancellationToken);
if (!response.IsSuccessStatusCode)
{
- _logger.LogError($"Message Failed { response.StatusCode}, {response.Content.ReadAsStringAsync()}");
+ string errorBody = await response.Content.ReadAsStringAsync();
+ _logger.LogError(
+ "❌ Webhook call failed for RequestId {RequestId}. WebHookName {WebHookName}, Endpoint {Endpoint}. Status={StatusCode}, Response={Body}",
+ requestId,
+ webHookName,
+ endpoint,
+ response.StatusCode,
+ errorBody);
+
if (response.StatusCode != System.Net.HttpStatusCode.InternalServerError
&& response.StatusCode != System.Net.HttpStatusCode.BadRequest)
{
- throw new Exception($"Message Failed {response.StatusCode}, {response.Content.ReadAsStringAsync()}");
+ throw new Exception($"Message Failed {response.StatusCode}, {errorBody}");
}
}
-
- _logger.LogInformation("get response successfully from webhook.");
-
+ else
+ {
+ _logger.LogInformation(
+ "✅ Webhook acknowledged successfully for RequestId {RequestId}. Endpoint: {Endpoint}",
+ requestId,
+ endpoint);
+ }
}
catch (Exception exception)
{
- _logger.LogError($"NotifyNotificationAcknowledged {exception.Message}");
+ _logger.LogError(exception,
+ "❌ NotifyNotificationAcknowledged failed for RequestId {RequestId}: {Message}",
+ requestId,
+ exception.Message);
throw;
}
}
+ _logger.LogDebug("🏁 End NotifyNotificationAcknowledged for RequestId: {RequestId}", requestId);
}
}
diff --git a/app/SearchApi/SearchRequest.Adaptor/Program.cs b/app/SearchApi/SearchRequest.Adaptor/Program.cs
index a88ea876..9047591a 100644
--- a/app/SearchApi/SearchRequest.Adaptor/Program.cs
+++ b/app/SearchApi/SearchRequest.Adaptor/Program.cs
@@ -59,7 +59,7 @@ public static void Main(string[] args)
var builder = Host.CreateDefaultBuilder(args)
.UseSerilog((hostingContext, loggerConfiguration) =>
{
- string serviceName = hostingContext.Configuration["JAEGER_SERVICE_NAME"];
+ string serviceName = hostingContext.Configuration["JAEGER_SERVICE_NAME"] ?? "request-api";
loggerConfiguration
.ReadFrom.Configuration(hostingContext.Configuration)