Skip to content

Commit 842cf72

Browse files
Merge pull request #881 from bcgov/feature/registration-validation
Listing Report Reg Check
2 parents 37a661e + 5ee4235 commit 842cf72

File tree

8 files changed

+150
-79
lines changed

8 files changed

+150
-79
lines changed

server/StrDss.Data/Mappings/ModelToEntityProfile.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public ModelToEntityProfile()
2323
.ForMember(dest => dest.PlatformListingNo, opt => opt.MapFrom(src => src.ListingId))
2424
.ForMember(dest => dest.PlatformListingUrl, opt => opt.MapFrom(src => src.ListingUrl))
2525
.ForMember(dest => dest.BusinessLicenceNo, opt => opt.MapFrom(src => src.BusLicNo))
26-
.ForMember(dest => dest.BcRegistryNo, opt => opt.MapFrom(src => src.BcRegNo))
26+
.ForMember(dest => dest.BcRegistryNo, opt => opt.MapFrom(src => src.RegNo))
2727
.ForMember(dest => dest.IsEntireUnit, opt => opt.MapFrom(src => src.IsEntireUnit == "Y"))
2828
.ForMember(dest => dest.AvailableBedroomsQty, opt => opt.MapFrom(src => CommonUtils.StringToShort(src.BedroomsQty)))
2929
.ForMember(dest => dest.NightsBookedQty, opt => opt.MapFrom(src => CommonUtils.StringToShort(src.NightsBookedQty)))

server/StrDss.Service/HttpClients/RegistrationAPI/BaseRegistrationApiClient.cs renamed to server/StrDss.Service/HttpClients/RegistrationAPI/BaseRegistrationApi.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
namespace StrDss.Service.HttpClients
88
{
9-
public abstract class BaseRegistrationApiClient
9+
public abstract class BaseRegistrationApi
1010
{
1111
private string _apiKey;
1212

server/StrDss.Service/HttpClients/RegistrationAPI/RegistrationApi.g.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ public partial interface IRegistrationApiClient
139139
}
140140

141141
[System.CodeDom.Compiler.GeneratedCode("NSwag", "14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))")]
142-
public partial class RegistrationApiClient : StrDss.Service.HttpClients.BaseRegistrationApiClient, IRegistrationApiClient
142+
public partial class RegistrationApiClient : StrDss.Service.HttpClients.BaseRegistrationApi, IRegistrationApiClient
143143
{
144144
private System.Net.Http.HttpClient _httpClient;
145145
private static System.Lazy<Newtonsoft.Json.JsonSerializerSettings> _settings = new System.Lazy<Newtonsoft.Json.JsonSerializerSettings>(CreateSerializerSettings, true);

server/StrDss.Service/HttpClients/RegistrationAPI/registration-api.nswag

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
},
1212
"codeGenerators": {
1313
"openApiToCSharpClient": {
14-
"clientBaseClass": "StrDss.Service.HttpClients.BaseRegistrationApiClient",
14+
"clientBaseClass": "StrDss.Service.HttpClients.BaseRegistrationApi",
1515
"configurationClass": null,
1616
"generateClientClasses": true,
1717
"suppressClientClassesOutput": false,
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
using Microsoft.Extensions.Logging;
2+
using StrDss.Common;
3+
using StrDss.Data.Entities;
4+
using StrDss.Model;
5+
using StrDss.Service.HttpClients;
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Linq;
9+
using System.Text;
10+
using System.Threading.Tasks;
11+
using Microsoft.Extensions.Configuration;
12+
using Microsoft.Extensions.Logging;
13+
using System.Text.RegularExpressions;
14+
using StrDss.Service;
15+
16+
namespace StrDss.Service
17+
{
18+
public interface IPermitValidationService
19+
{
20+
Task<(bool isValid, Dictionary<string, List<string>> errors)> ValidateRegistrationPermitAsync(string regNo, string unitNumber, string streetNumber, string postalCode);
21+
}
22+
}
23+
public class PermitValidationService : IPermitValidationService
24+
{
25+
private IRegistrationApiClient _regApiClient;
26+
private IConfiguration _config;
27+
private ILogger<StrDssLogger> _logger;
28+
private readonly string? _apiAccount;
29+
30+
public PermitValidationService(IRegistrationApiClient regApiClient, IConfiguration config, ILogger<StrDssLogger> logger)
31+
{
32+
_regApiClient = regApiClient;
33+
_config = config;
34+
_logger = logger;
35+
_apiAccount = _config.GetValue<string>("REGISTRATION_API_ACCOUNT");
36+
}
37+
38+
public async Task<(bool isValid, Dictionary<string, List<string>> errors)> ValidateRegistrationPermitAsync(string regNo, string unitNumber, string streetNumber, string postalCode)
39+
{
40+
bool isValid = true;
41+
Dictionary<string, List<string>> errorDetails = new();
42+
43+
Body body = new()
44+
{
45+
Identifier = regNo,
46+
Address = new()
47+
{
48+
UnitNumber = unitNumber,
49+
StreetNumber = streetNumber,
50+
PostalCode = postalCode
51+
}
52+
};
53+
54+
try
55+
{
56+
Response resp = await _regApiClient.ValidatePermitAsync(body, _apiAccount);
57+
58+
// If we didn't get a Status field back, then there was an error
59+
if (string.IsNullOrEmpty(resp.Status))
60+
{
61+
isValid = false;
62+
if (resp.Errors.Count == 0)
63+
errorDetails.Add("UNKNOWN ERROR", new List<string> { "Response did not contain a status or error message." });
64+
else
65+
errorDetails = resp.Errors
66+
.GroupBy(e => e.Code)
67+
.ToDictionary(g => g.Key, g => g.Select(e => e.Message).ToList());
68+
}
69+
else if (!string.Equals(resp.Status, "ACTIVE", StringComparison.OrdinalIgnoreCase))
70+
{
71+
isValid = false;
72+
errorDetails.Add("INACTIVE PERMIT", new List<string> { "Error: registration status returned as " + resp.Status });
73+
}
74+
}
75+
catch (ApiException ex)
76+
{
77+
isValid = false;
78+
if (ex.StatusCode == 404)
79+
{
80+
errorDetails.Add("NOT FOUND", new List<string> { "Error: Permit not found (404)." });
81+
}
82+
else if (ex.StatusCode == 401)
83+
{
84+
errorDetails.Add("UNAUTHORIZED", new List<string> { "Error: Unauthorized access (401)." });
85+
}
86+
else
87+
{
88+
errorDetails.Add("EXCEPTION", new List<string> { "Error: Service threw an undhandled exception." });
89+
}
90+
}
91+
catch (Exception ex)
92+
{
93+
isValid = false;
94+
errorDetails.Add("EXCEPTION", new List<string> { "Error: Service threw an undhandled exception." });
95+
}
96+
97+
return (isValid, errorDetails);
98+
}
99+
}

server/StrDss.Service/RegistrationService.cs

Lines changed: 8 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,14 @@
33
using Microsoft.AspNetCore.Http;
44
using Microsoft.Extensions.Configuration;
55
using Microsoft.Extensions.Logging;
6-
using NetTopologySuite.Geometries;
76
using StrDss.Common;
87
using StrDss.Data;
98
using StrDss.Data.Entities;
109
using StrDss.Data.Repositories;
1110
using StrDss.Model;
12-
using StrDss.Model.OrganizationDtos;
1311
using StrDss.Model.RentalReportDtos;
1412
using StrDss.Service.CsvHelpers;
1513
using StrDss.Service.EmailTemplates;
16-
using StrDss.Service.HttpClients;
1714
using System.Diagnostics;
1815
using System.Text;
1916

@@ -32,12 +29,12 @@ public class RegistrationService : ServiceBase, IRegistrationService
3229
private IEmailMessageService _emailService;
3330
private IEmailMessageRepository _emailRepo;
3431
private IConfiguration _config;
35-
private IRegistrationApiClient _regClient;
32+
private IPermitValidationService _permitValidation;
3633
private string? _apiAccount;
3734

3835
public RegistrationService(ICurrentUser currentUser, IFieldValidatorService validator, IUnitOfWork unitOfWork, IMapper mapper, IHttpContextAccessor httpContextAccessor,
3936
IUploadDeliveryRepository uploadRepo, IRentalListingReportRepository reportRepo, IPhysicalAddressRepository addressRepo,
40-
IUserRepository userRepo, IEmailMessageService emailService, IEmailMessageRepository emailRepo, IConfiguration config, IRegistrationApiClient regClient,
37+
IUserRepository userRepo, IEmailMessageService emailService, IEmailMessageRepository emailRepo, IConfiguration config, IPermitValidationService permitValidation,
4138
ILogger<StrDssLogger> logger)
4239
: base(currentUser, validator, unitOfWork, mapper, httpContextAccessor, logger)
4340
{
@@ -48,8 +45,7 @@ public RegistrationService(ICurrentUser currentUser, IFieldValidatorService vali
4845
_emailService = emailService;
4946
_emailRepo = emailRepo;
5047
_config = config;
51-
_regClient = regClient;
52-
_apiAccount = _config.GetValue<string>("REGISTRATION_API_ACCOUNT");
48+
_permitValidation = permitValidation;
5349
}
5450

5551
public async Task ProcessRegistrationDataUploadAsync(DssUploadDelivery upload)
@@ -132,7 +128,7 @@ public async Task ProcessRegistrationDataUploadAsync(DssUploadDelivery upload)
132128
{
133129
UserName = $"{user!.GivenNm}",
134130
NumErrors = errorCount,
135-
Link = GetHostUrl() + "/upload-listing-history",
131+
Link = GetHostUrl() + "/registration-validation-history",
136132
To = new string[] { user!.EmailAddressDsc! },
137133
Info = $"{EmailMessageTypes.ListingUploadError} for {user.FamilyNm}, {user.GivenNm}",
138134
From = adminEmail
@@ -179,7 +175,7 @@ private async Task<bool> ProcessUploadLine(DssUploadDelivery upload, long OrgId,
179175
return false;
180176
}
181177

182-
(bool isValid, Dictionary<string, List<string>> regErrors) = await ValidateRegistrationPermitAsync(row.RegNo, row.RentalUnit, row.RentalStreet, row.RentalPostal);
178+
(bool isValid, Dictionary<string, List<string>> regErrors) = await _permitValidation.ValidateRegistrationPermitAsync(row.RegNo, row.RentalUnit, row.RentalStreet, row.RentalPostal);
183179
if (isValid)
184180
{
185181
if (!string.IsNullOrEmpty(row.ListingId))
@@ -204,63 +200,15 @@ private async Task<bool> ProcessUploadLine(DssUploadDelivery upload, long OrgId,
204200
}
205201
}
206202

207-
SaveUploadLine(uploadLine, regErrors, false, !isValid);
203+
SaveUploadLine(uploadLine, regErrors, false, !isValid, false);
208204
return isValid;
209205
}
210206

211-
public async Task<(bool isValid, Dictionary<string, List<string>> errors)> ValidateRegistrationPermitAsync(string regNo, string unitNumber, string streetNumber, string postalCode)
212-
{
213-
bool isValid = true;
214-
Dictionary<string, List<string>> errorDetails = new();
215-
216-
StrDss.Service.HttpClients.Body body = new()
217-
{
218-
Identifier = regNo,
219-
Address = new()
220-
{
221-
UnitNumber = unitNumber,
222-
StreetNumber = streetNumber,
223-
PostalCode = postalCode
224-
}
225-
};
226-
227-
try
228-
{
229-
Response resp = await _regClient.ValidatePermitAsync(body, _apiAccount);
230-
231-
// If we didn't get a Status field back, then there was an error
232-
if (string.IsNullOrEmpty(resp.Status))
233-
{
234-
isValid = false;
235-
if (resp.Errors.Count == 0)
236-
errorDetails.Add("UNKNOWN ERROR", new List<string> { "Response did not contain a status or error message." });
237-
else
238-
errorDetails = resp.Errors
239-
.GroupBy(e => e.Code)
240-
.ToDictionary(g => g.Key, g => g.Select(e => e.Message).ToList());
241-
}
242-
243-
// If the status is not "ACTIVE" then there is an issue with the registration
244-
if (!string.Equals(resp.Status, "ACTIVE", StringComparison.OrdinalIgnoreCase))
245-
{
246-
isValid = false;
247-
errorDetails.Add("INACTIVE PERMIT", new List<string> { "Error: registration status returned as " + resp.Status });
248-
}
249-
}
250-
catch (Exception ex)
251-
{
252-
isValid = false;
253-
errorDetails.Add("EXCEPTION", new List<string> { ex.Message });
254-
}
255-
256-
return (isValid, errorDetails);
257-
}
258-
259-
private void SaveUploadLine(DssUploadLine uploadLine, Dictionary<string, List<string>> errors, bool isValidationFailure, bool isSystemError)
207+
private void SaveUploadLine(DssUploadLine uploadLine, Dictionary<string, List<string>> errors, bool isValidationFailure, bool isSystemError, bool useUnderscores = true)
260208
{
261209
uploadLine.IsValidationFailure = isValidationFailure;
262210
uploadLine.IsSystemFailure = isSystemError;
263-
uploadLine.ErrorTxt = errors.ParseErrorWithUnderScoredKeyName();
211+
uploadLine.ErrorTxt = useUnderscores ? errors.ParseErrorWithUnderScoredKeyName() : errors.ParseError();
264212
uploadLine.IsProcessed = true;
265213
_unitOfWork.Commit();
266214
}

server/StrDss.Service/RentalListingReportService.cs

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,13 @@ public class RentalListingReportService : ServiceBase, IRentalListingReportServi
3535
private IEmailMessageService _emailService;
3636
private IEmailMessageRepository _emailRepo;
3737
private IBizLicenceRepository _bizLicRepo;
38+
private IPermitValidationService _permitValidation;
3839
private IConfiguration _config;
3940

4041
public RentalListingReportService(ICurrentUser currentUser, IFieldValidatorService validator, IUnitOfWork unitOfWork, IMapper mapper, IHttpContextAccessor httpContextAccessor,
4142
IOrganizationRepository orgRepo, IUploadDeliveryRepository uploadRepo, IRentalListingReportRepository reportRepo, IPhysicalAddressRepository addressRepo,
4243
IGeocoderApi geocoder, IUserRepository userRepo, IEmailMessageService emailService, IEmailMessageRepository emailRepo, IBizLicenceRepository bizLicRepo,
43-
IConfiguration config, ILogger<StrDssLogger> logger)
44+
IPermitValidationService permitValidation, IConfiguration config, ILogger<StrDssLogger> logger)
4445
: base(currentUser, validator, unitOfWork, mapper, httpContextAccessor, logger)
4546
{
4647
_orgRepo = orgRepo;
@@ -52,6 +53,7 @@ public RentalListingReportService(ICurrentUser currentUser, IFieldValidatorServi
5253
_emailService = emailService;
5354
_emailRepo = emailRepo;
5455
_bizLicRepo = bizLicRepo;
56+
_permitValidation = permitValidation;
5557
_config = config;
5658
}
5759

@@ -216,6 +218,17 @@ private async Task<bool> ProcessUploadLine(DssRentalListingReport report, DssUpl
216218
return false;
217219
}
218220

221+
// Should we validate the Registration?
222+
bool isRegistrationValid = false;
223+
if (_currentUser.Permissions.Any(p => p == Permissions.ValidateRegistration))
224+
{
225+
// Do we have what we need to validate the registration?
226+
if (!string.IsNullOrEmpty(row.RegNo) && !string.IsNullOrEmpty(row.RentalStreet) && !string.IsNullOrEmpty(row.RentalPostal))
227+
{
228+
(isRegistrationValid, Dictionary<string, List<string>> regErrors) = await _permitValidation.ValidateRegistrationPermitAsync(row.RegNo, row.RentalUnit, row.RentalStreet, row.RentalPostal);
229+
}
230+
}
231+
219232
var offeringOrg = await _orgRepo.GetOrganizationByOrgCdAsync(row.OrgCd); //already validated in the file upload
220233

221234
using var tran = _unitOfWork.BeginTransaction();
@@ -224,7 +237,7 @@ private async Task<bool> ProcessUploadLine(DssRentalListingReport report, DssUpl
224237

225238
AddContacts(listing, row);
226239

227-
var (physicalAddress, systemError) = await CreateOrGetPhysicalAddress(listing, row);
240+
var (physicalAddress, systemError) = await CreateOrGetPhysicalAddress(listing, row, isRegistrationValid);
228241

229242
listing.LocatingPhysicalAddress = physicalAddress;
230243

@@ -262,6 +275,8 @@ private async Task<bool> ProcessUploadLine(DssRentalListingReport report, DssUpl
262275
}
263276

264277
tran.Commit();
278+
279+
265280
return true;
266281
}
267282

@@ -300,7 +315,7 @@ private async Task<DssRentalListing> CreateOrUpdateRentalListing(DssRentalListin
300315
return listing;
301316
}
302317

303-
private async Task<(DssPhysicalAddress, string)> CreateOrGetPhysicalAddress(DssRentalListing listing, RentalListingRowUntyped row)
318+
private async Task<(DssPhysicalAddress, string)> CreateOrGetPhysicalAddress(DssRentalListing listing, RentalListingRowUntyped row, bool isRegistrationValid)
304319
{
305320
var address = row.RentalAddress;
306321

@@ -312,10 +327,7 @@ private async Task<DssRentalListing> CreateOrUpdateRentalListing(DssRentalListin
312327
{
313328
var newAddress = new DssPhysicalAddress
314329
{
315-
OriginalAddressTxt = row.RentalAddress,
316-
RegRentalUnitNo = row.RentalUnit,
317-
RegRentalStreetNo = row.RentalStreet,
318-
RegRentalPostalCode = row.RentalPostal,
330+
OriginalAddressTxt = row.RentalAddress
319331
};
320332

321333
error = await _geocoder.GetAddressAsync(newAddress);
@@ -350,9 +362,12 @@ private async Task<DssRentalListing> CreateOrUpdateRentalListing(DssRentalListin
350362

351363
listing.IsChangedOriginalAddress = true;
352364

353-
newAddress.RegRentalUnitNo = row.RentalUnit;
354-
newAddress.RegRentalStreetNo = row.RentalStreet;
355-
newAddress.RegRentalPostalCode = row.RentalPostal;
365+
if (isRegistrationValid)
366+
{
367+
newAddress.RegRentalUnitNo = row.RentalUnit;
368+
newAddress.RegRentalStreetNo = row.RentalStreet;
369+
newAddress.RegRentalPostalCode = row.RentalPostal;
370+
}
356371

357372
_addressRepo.ReplaceAddress(listing, newAddress);
358373

@@ -363,12 +378,16 @@ private async Task<DssRentalListing> CreateOrUpdateRentalListing(DssRentalListin
363378
{
364379
var newAddress = new DssPhysicalAddress
365380
{
366-
OriginalAddressTxt = row.RentalAddress,
367-
RegRentalUnitNo = row.RentalUnit,
368-
RegRentalStreetNo = row.RentalStreet,
369-
RegRentalPostalCode = row.RentalPostal,
381+
OriginalAddressTxt = row.RentalAddress
370382
};
371383

384+
if (isRegistrationValid)
385+
{
386+
newAddress.RegRentalUnitNo = row.RentalUnit;
387+
newAddress.RegRentalStreetNo = row.RentalStreet;
388+
newAddress.RegRentalPostalCode = row.RentalPostal;
389+
}
390+
372391
error = await _geocoder.GetAddressAsync(newAddress);
373392

374393
if (error.IsEmpty() && newAddress.LocationGeometry is not null && newAddress.LocationGeometry is Point point)

server/StrDss.Service/UploadDeliveryService.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,12 @@ public async Task<PagedDto<UploadHistoryViewDto>> GetUploadHistory(long? orgId,
573573
foreach (var lineId in lines)
574574
{
575575
var line = await _uploadRepo.GetUploadLineWithError(lineId);
576-
contents.AppendLine($"\"{line.ErrorText ?? "Success"}\"," + line.LineText.TrimEndNewLine());
576+
var text = line.ErrorText;
577+
if (string.IsNullOrEmpty(text))
578+
{
579+
text = "Success";
580+
}
581+
contents.AppendLine($"\"{text}\"," + line.LineText.TrimEndNewLine());
577582
}
578583

579584
return Encoding.UTF8.GetBytes(contents.ToString());

0 commit comments

Comments
 (0)