diff --git a/jobs/batch-permit-validator/poetry.lock b/jobs/batch-permit-validator/poetry.lock index b4cfd027b..d8e9dcc66 100644 --- a/jobs/batch-permit-validator/poetry.lock +++ b/jobs/batch-permit-validator/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. [[package]] name = "alembic" @@ -3269,7 +3269,7 @@ files = [ [[package]] name = "strr-api" -version = "0.0.70" +version = "0.0.77" description = "" optional = false python-versions = "^3.11" @@ -3306,8 +3306,8 @@ weasyprint = "^62.3" [package.source] type = "git" url = "https://github.com/bcgov/STRR.git" -reference = "main" -resolved_reference = "d2c10d980cd73a20ccbad77c06402ab8041a4314" +reference = "feature-provicinal-workflow" +resolved_reference = "789c6bd51cfcff771267f6f11e5dfb081db69795" subdirectory = "strr-api" [[package]] @@ -3700,4 +3700,4 @@ test = ["pytest"] [metadata] lock-version = "2.0" python-versions = "^3.12.2" -content-hash = "92ef7ce52a478d7b4826ddabf412543d4534adcc14e7b035572a0c4af80d98ce" +content-hash = "1c43a0ba7adea3a9f6b626b506cba65f207226cd951170a17ba330b7db1ab39c" diff --git a/jobs/batch-permit-validator/pyproject.toml b/jobs/batch-permit-validator/pyproject.toml index 51fbf0332..035a4bc21 100644 --- a/jobs/batch-permit-validator/pyproject.toml +++ b/jobs/batch-permit-validator/pyproject.toml @@ -20,7 +20,7 @@ gunicorn = "^21.2.0" pg8000 = "^1.31.2" gcp-queue = { git = "https://github.com/bcgov/sbc-connect-common.git", subdirectory = "python/gcp-queue", branch = "main" } structured-logging = { git = "https://github.com/bcgov/sbc-connect-common.git", subdirectory = "python/structured-logging", branch = "main" } -strr-api = {git = "https://github.com/bcgov/STRR.git", rev = "main", subdirectory = "strr-api"} +strr-api = {git = "https://github.com/bcgov/STRR.git", branch = "feature-provicinal-workflow", subdirectory = "strr-api"} nanoid = "^2.0.0" flake8-pyproject = "^1.2.3" redis = "^5.2.1" diff --git a/strr-api/pyproject.toml b/strr-api/pyproject.toml index dd6b17840..16a9542d4 100644 --- a/strr-api/pyproject.toml +++ b/strr-api/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "strr-api" -version = "0.0.74" +version = "0.0.77" description = "" authors = ["thorwolpert "] license = "BSD 3-Clause" diff --git a/strr-api/src/strr_api/services/application_service.py b/strr-api/src/strr_api/services/application_service.py index e3ccf92a8..b3ede0a1b 100644 --- a/strr-api/src/strr_api/services/application_service.py +++ b/strr-api/src/strr_api/services/application_service.py @@ -70,6 +70,8 @@ Application.Status.PROVISIONAL, Application.Status.NOC_PENDING, Application.Status.NOC_EXPIRED, + Application.Status.PROVISIONAL_REVIEW_NOC_PENDING, + Application.Status.PROVISIONAL_REVIEW_NOC_EXPIRED, Application.Status.ADDITIONAL_INFO_REQUESTED, Application.Status.FULL_REVIEW_APPROVED, Application.Status.PROVISIONALLY_APPROVED, diff --git a/strr-api/src/strr_api/services/validation_service.py b/strr-api/src/strr_api/services/validation_service.py index 81585f079..aaaf380f6 100644 --- a/strr-api/src/strr_api/services/validation_service.py +++ b/strr-api/src/strr_api/services/validation_service.py @@ -31,6 +31,7 @@ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. +# pylint: disable=R0914 """Permit Validation Service.""" import copy import json @@ -92,7 +93,8 @@ def check_permit_details(cls, request_json: dict, registration: Registration): if registration.registration_type == RegistrationType.HOST.value: if ( registration.rental_property.address.street_number - and str(address_json.get("streetNumber")) != registration.rental_property.address.street_number + and str(address_json.get("streetNumber")).lower() + != registration.rental_property.address.street_number.lower() ): errors.append( { @@ -100,48 +102,61 @@ def check_permit_details(cls, request_json: dict, registration: Registration): "message": ErrorMessage.STREET_NUMBER_MISMATCH.value, } ) - if ( - address_json.get("postalCode", "").replace(" ", "").lower() - != registration.rental_property.address.postal_code.replace(" ", "").lower() + + # Postal code validation + request_postal_code = address_json.get("postalCode", "").replace(" ", "").lower() + permit_postal_code = registration.rental_property.address.postal_code.replace(" ", "").lower() + if not ( + request_postal_code == permit_postal_code + or (len(request_postal_code) >= 4 and (request_postal_code[:4] == permit_postal_code[:4])) ): errors.append( {"code": ErrorMessage.POSTAL_CODE_MISMATCH.name, "message": ErrorMessage.POSTAL_CODE_MISMATCH.value} ) - if unit_number := address_json.get("unitNumber", None): - reg_unit_number = registration.rental_property.address.unit_number - if reg_unit_number and reg_unit_number.startswith("#"): - reg_unit_number = reg_unit_number[1:] - if unit_number and unit_number.startswith("#"): - unit_number = unit_number[1:] - if unit_number != reg_unit_number: - errors.append( - { - "code": ErrorMessage.UNIT_NUMBER_MISMATCH.name, - "message": ErrorMessage.UNIT_NUMBER_MISMATCH.value, - } - ) + # Unit number validation. + has_unit_number_validation_error = False + if input_unit_number := address_json.get("unitNumber", None): + if permit_unit_number := registration.rental_property.address.unit_number: + filtered_input_unit_number = ValidationService._get_filtered_unit_number(input_unit_number) + filtered_permit_unit_number = ValidationService._get_filtered_unit_number(permit_unit_number) + if filtered_input_unit_number != filtered_permit_unit_number: + has_unit_number_validation_error = True + else: + has_unit_number_validation_error = True + + else: + if registration.rental_property.address.unit_number: + has_unit_number_validation_error = True + + if has_unit_number_validation_error: + errors.append( + { + "code": ErrorMessage.UNIT_NUMBER_MISMATCH.name, + "message": ErrorMessage.UNIT_NUMBER_MISMATCH.value, + } + ) if registration.registration_type == RegistrationType.STRATA_HOTEL.value: match_found = False strata_hotel = registration.strata_hotel_registration.strata_hotel location = strata_hotel.location - if ( - str(address_json.get("streetNumber")) - == str(cls._extract_street_number(cls._get_text_after_hyphen(location.street_address))) - and address_json.get("postalCode", "").replace(" ", "").lower() - == location.postal_code.replace(" ", "").lower() + if str(address_json.get("streetNumber")) == str( + cls._extract_street_number(cls._get_text_after_hyphen(location.street_address)) + ) and ( + address_json.get("postalCode", "").replace(" ", "").lower()[:4] + == location.postal_code.replace(" ", "").lower()[:4] ): match_found = True if not match_found: for building in strata_hotel.buildings: - if ( - str(address_json.get("streetNumber")) - == str(cls._extract_street_number(cls._get_text_after_hyphen(building.address.street_address))) - and address_json.get("postalCode", "").replace(" ", "").lower() - == building.address.postal_code.replace(" ", "").lower() + if str(address_json.get("streetNumber")) == str( + cls._extract_street_number(cls._get_text_after_hyphen(building.address.street_address)) + ) and ( + address_json.get("postalCode", "").replace(" ", "").lower()[:4] + == building.address.postal_code.replace(" ", "").lower()[:4] ): match_found = True break @@ -162,6 +177,28 @@ def check_permit_details(cls, request_json: dict, registration: Registration): response["validUntil"] = DateUtil.as_legislation_timezone(registration.expiry_date).strftime("%Y-%m-%d") return response, status_code + @classmethod + def _get_filtered_unit_number(cls, unit_number): + # Remove leading # or - + unit_number = re.sub(r"^[#-]+\s*", "", unit_number, flags=re.IGNORECASE) + + # Remove keywords (case-insensitive): Suite, Unit, SL, Strata Lot, Room, Cabin, No. + unit_number = re.sub( + r"\b(Suite|Unit|SL|Strata Lot|Room|Lot|RM|Cabin|Bldg|ste|Nbr|Unt|Apartment|Apt|Number|Num|Floor|Flr|Fl|BUILDING|No\.?)", # noqa: E501 + "", + unit_number, + flags=re.IGNORECASE, + ) + + # Remove all hyphens and spaces (including in-between) + unit_number = re.sub(r"[-.\s]+", "", unit_number, flags=re.IGNORECASE) + + # Remove standalone leading zeros + unit_number = re.sub(r"\b0+(\w+)", r"\1", unit_number, flags=re.IGNORECASE) + + # Convert to uppercase + return unit_number.upper() + @classmethod def _get_text_after_hyphen(cls, address_line): if "-" in address_line: diff --git a/strr-api/tests/unit/resources/test_validation.py b/strr-api/tests/unit/resources/test_validation.py index a3fa43e91..ecc35d0f7 100644 --- a/strr-api/tests/unit/resources/test_validation.py +++ b/strr-api/tests/unit/resources/test_validation.py @@ -108,7 +108,7 @@ def test_permit_details_mismatch(session, client, jwt): validate_permit_request = { "identifier": registration_number, - "address": {"streetNumber": "12165", "postalCode": "V2X 7N2"}, + "address": {"streetNumber": "12165", "postalCode": "V2X 555"}, } rv = client.post("/permits/:validatePermit", json=validate_permit_request, headers=headers) assert rv.status_code == HTTPStatus.BAD_REQUEST @@ -125,6 +125,20 @@ def test_permit_details_mismatch(session, client, jwt): response_json.get("errors")[1].get("message") == "Postal code does not match with the data in the permit." ) + validate_permit_request = { + "identifier": registration_number, + "address": {"streetNumber": "12165", "postalCode": "V2X 7N2"}, + } + rv = client.post("/permits/:validatePermit", json=validate_permit_request, headers=headers) + assert rv.status_code == HTTPStatus.BAD_REQUEST + response_json = rv.json + + assert len(response_json.get("errors")) == 1 + assert response_json.get("errors")[0].get("code") == "STREET_NUMBER_MISMATCH" + assert ( + response_json.get("errors")[0].get("message") == "Street number does not match with the data in the permit." + ) + def test_invalid_request_with_identifier(session, client, jwt): headers = create_header(jwt, [STRR_EXAMINER], "Account-Id")