diff --git a/strr-api/migrations/versions/20241031_2216_1ddad4ce0e21_.py b/strr-api/migrations/versions/20241031_2216_1ddad4ce0e21_.py new file mode 100644 index 000000000..de0faf379 --- /dev/null +++ b/strr-api/migrations/versions/20241031_2216_1ddad4ce0e21_.py @@ -0,0 +1,46 @@ +"""empty message + +Revision ID: 1ddad4ce0e21 +Revises: 14f84cc80367 +Create Date: 2024-10-31 22:16:54.409155 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '1ddad4ce0e21' +down_revision = '14f84cc80367' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + + contacttype = postgresql.ENUM('INDIVIDUAL', 'BUSINESS', name='contacttype') + contacttype.create(op.get_bind(), checkfirst=True) + + with op.batch_alter_table('property_contacts', schema=None) as batch_op: + batch_op.add_column(sa.Column('contact_type', contacttype, nullable=True)) + batch_op.add_column(sa.Column('business_legal_name', sa.String(length=1000), nullable=True)) + + with op.batch_alter_table('property_contacts_history', schema=None) as batch_op: + batch_op.add_column(sa.Column('contact_type', contacttype, autoincrement=False, nullable=True)) + batch_op.add_column(sa.Column('business_legal_name', sa.String(length=1000), autoincrement=False, nullable=True)) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('property_contacts_history', schema=None) as batch_op: + batch_op.drop_column('business_legal_name') + batch_op.drop_column('contact_type') + + with op.batch_alter_table('property_contacts', schema=None) as batch_op: + batch_op.drop_column('business_legal_name') + batch_op.drop_column('contact_type') + + # ### end Alembic commands ### diff --git a/strr-api/pyproject.toml b/strr-api/pyproject.toml index 7fccb9f90..a803900d6 100644 --- a/strr-api/pyproject.toml +++ b/strr-api/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "strr-api" -version = "0.0.18" +version = "0.0.19" description = "" authors = ["thorwolpert "] license = "BSD 3-Clause" diff --git a/strr-api/src/strr_api/models/rental.py b/strr-api/src/strr_api/models/rental.py index 590d92440..ec64f85b8 100644 --- a/strr-api/src/strr_api/models/rental.py +++ b/strr-api/src/strr_api/models/rental.py @@ -117,10 +117,18 @@ class PropertyManager(Versioned, BaseModel): class PropertyContact(Versioned, BaseModel): """Property Contacts""" + class ContactType(BaseEnum): + """Enum of host residence option.""" + + INDIVIDUAL = auto() # pylint: disable=invalid-name + BUSINESS = auto() # pylint: disable=invalid-name + __tablename__ = "property_contacts" id = db.Column(db.Integer, primary_key=True, autoincrement=True) is_primary = db.Column(db.Boolean, nullable=False, default=False) + contact_type = db.Column(db.Enum(ContactType), default=ContactType.INDIVIDUAL) + business_legal_name = db.Column(db.String(1000), nullable=True) contact_id = db.Column(db.Integer, db.ForeignKey("contacts.id"), nullable=False) property_id = db.Column(db.Integer, db.ForeignKey("rental_properties.id"), nullable=False) diff --git a/strr-api/src/strr_api/requests/RegistrationRequest.py b/strr-api/src/strr_api/requests/RegistrationRequest.py index 9d8258c65..90a9a71a8 100644 --- a/strr-api/src/strr_api/requests/RegistrationRequest.py +++ b/strr-api/src/strr_api/requests/RegistrationRequest.py @@ -173,13 +173,25 @@ def __init__(self, phoneNumber, emailAddress, preferredName=None, extension=None class Contact: """Contact payload object.""" - def __init__(self, name, dateOfBirth, details, mailingAddress, socialInsuranceNumber=None, businessNumber=None): + def __init__( + self, + name, + dateOfBirth, + details, + mailingAddress, + socialInsuranceNumber=None, + businessNumber=None, + businessLegalName=None, + contactType=None, + ): self.name = ContactName(**name) self.dateOfBirth = dateOfBirth self.socialInsuranceNumber = socialInsuranceNumber self.businessNumber = businessNumber self.details = ContactDetails(**details) self.mailingAddress = MailingAddress(**mailingAddress) + self.businessLegalName = businessLegalName + self.contactType = contactType class Document: diff --git a/strr-api/src/strr_api/responses/RegistrationSerializer.py b/strr-api/src/strr_api/responses/RegistrationSerializer.py index fdd5438d1..c52995c30 100644 --- a/strr-api/src/strr_api/responses/RegistrationSerializer.py +++ b/strr-api/src/strr_api/responses/RegistrationSerializer.py @@ -114,6 +114,8 @@ def populate_host_registration_details(cls, registration_data: dict, registratio "dateOfBirth": primary_property_contact.contact.date_of_birth, "socialInsuranceNumber": primary_property_contact.contact.social_insurance_number, "businessNumber": primary_property_contact.contact.business_number, + "contactType": primary_property_contact.contact_type, + "businessLegalName": primary_property_contact.business_legal_name, "details": { "preferredName": primary_property_contact.contact.preferredname, "phoneNumber": primary_property_contact.contact.phone_number, @@ -141,6 +143,7 @@ def populate_host_registration_details(cls, registration_data: dict, registratio }, "dateOfBirth": secondary_property_contact.contact.date_of_birth, "socialInsuranceNumber": secondary_property_contact.contact.social_insurance_number, + "contactType": secondary_property_contact.contact_type, "businessNumber": secondary_property_contact.contact.business_number, "details": { "preferredName": secondary_property_contact.contact.preferredname, diff --git a/strr-api/src/strr_api/schemas/schemas/host-registration.json b/strr-api/src/strr_api/schemas/schemas/host-registration.json index 8f6097ed0..508213289 100644 --- a/strr-api/src/strr_api/schemas/schemas/host-registration.json +++ b/strr-api/src/strr_api/schemas/schemas/host-registration.json @@ -39,6 +39,17 @@ "businessNumber": { "type": "string" }, + "businessLegalName": { + "type": "string" + }, + "contactType": { + "type": "string", + "enum": [ + "INDIVIDUAL", + "BUSINESS" + ], + "default": "INDIVIDUAL" + }, "details": { "type": "object", "properties": { diff --git a/strr-api/src/strr_api/services/registration_service.py b/strr-api/src/strr_api/services/registration_service.py index 67bb6bc6f..14881787d 100644 --- a/strr-api/src/strr_api/services/registration_service.py +++ b/strr-api/src/strr_api/services/registration_service.py @@ -227,6 +227,8 @@ def _create_host_registration(cls, registration_request: dict) -> RentalProperty primary_property_contact = PropertyContact() primary_property_contact.is_primary = True + primary_property_contact.contact_type = registration_request.primaryContact.contactType + primary_property_contact.business_legal_name = registration_request.primaryContact.businessLegalName primary_property_contact.contact = Contact( firstname=registration_request.primaryContact.name.firstName, lastname=registration_request.primaryContact.name.lastName, @@ -252,6 +254,7 @@ def _create_host_registration(cls, registration_request: dict) -> RentalProperty if registration_request.secondaryContact: secondary_property_contact = PropertyContact() secondary_property_contact.is_primary = False + secondary_property_contact.contact_type = registration_request.secondaryContact.contactType secondary_property_contact.contact = Contact( firstname=registration_request.secondaryContact.name.firstName, lastname=registration_request.secondaryContact.name.lastName, diff --git a/strr-api/tests/mocks/json/business_as_host.json b/strr-api/tests/mocks/json/business_as_host.json new file mode 100644 index 000000000..0659cf5e0 --- /dev/null +++ b/strr-api/tests/mocks/json/business_as_host.json @@ -0,0 +1,87 @@ +{ + "registration": { + "registrationType": "HOST", + "primaryContact": { + "contactType": "BUSINESS", + "businessLegalName": "Test Business", + "businessNumber": "123456789", + "name": { + "firstName": "The", + "middleName": "First", + "lastName": "Guy" + }, + "dateOfBirth": "1986-10-23", + "details": { + "preferredName": "Mickey", + "phoneNumber": "604-999-9999", + "extension": "x64", + "faxNumber": "604-777-7777", + "emailAddress": "test@test.test" + }, + "mailingAddress": { + "country": "CA", + "address": "12766 227st", + "addressLineTwo": "", + "city": "MAPLE RIDGE", + "province": "BC", + "postalCode": "V2X 6K6" + } + }, + "secondaryContact": { + "contactType": "INDIVIDUAL", + "name": { + "firstName": "The", + "middleName": "Other", + "lastName": "Guy" + }, + "dateOfBirth": "1986-10-23", + "details": { + "preferredName": "Mouse", + "phoneNumber": "604-888-8888", + "extension": "", + "faxNumber": "", + "emailAddress": "test2@test.test" + }, + "mailingAddress": { + "country": "CA", + "address": "12766 227st", + "addressLineTwo": "", + "city": "MAPLE RIDGE", + "province": "BC", + "postalCode": "V2X 6K6" + } + }, + "unitDetails": { + "parcelIdentifier": "000-460-991", + "businessLicense": "7777777", + "businessLicenseExpiryDate": "2025-01-01", + "propertyType": "SINGLE_FAMILY_HOME", + "ownershipType": "OWN", + "rentalUnitSpaceType": "ENTIRE_HOME", + "hostResidence": "SAME_UNIT", + "isUnitOnPrincipalResidenceProperty": true, + "numberOfRoomsForRent": 1 + }, + "unitAddress": { + "nickname": "My Rental Property", + "country": "CA", + "address": "12166 GREENWELL ST MAPLE RIDGE", + "addressLineTwo": "", + "city": "MAPLE RIDGE", + "province": "BC", + "postalCode": "V2X 7N1" + }, + "listingDetails": [ + { + "url": "https://www.airbnb.ca/rooms/26359027" + } + ], + "principalResidence": { + "isPrincipalResidence": true, + "agreedToRentalAct": true, + "nonPrincipalOption": "n/a", + "specifiedServiceProvider": "n/a", + "agreedToSubmit": true + } + } +} diff --git a/strr-api/tests/postman/strr-api.postman_collection.json b/strr-api/tests/postman/strr-api.postman_collection.json index a486c37cc..abebeba5c 100644 --- a/strr-api/tests/postman/strr-api.postman_collection.json +++ b/strr-api/tests/postman/strr-api.postman_collection.json @@ -189,6 +189,66 @@ }, "response": [] }, + { + "name": "Host Registration - Business as a host", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var jsonData = pm.response.json();", + "", + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + "});", + "", + "pm.environment.set(\"application_number\",jsonData.header.applicationNumber)" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Account-Id", + "value": "{{account_id}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"registration\": {\n \"registrationType\": \"HOST\",\n \"primaryContact\": {\n \"contactType\": \"BUSINESS\",\n \"businessLegalName\": \"Test Business\",\n \"businessNumber\": \"123456789\",\n \"name\": {\n \"firstName\": \"The\",\n \"middleName\": \"First\",\n \"lastName\": \"Guy\"\n },\n \"dateOfBirth\": \"1986-10-23\",\n \"details\": {\n \"preferredName\": \"Mickey\",\n \"phoneNumber\": \"604-999-9999\",\n \"extension\": \"x64\",\n \"faxNumber\": \"604-777-7777\",\n \"emailAddress\": \"test@test.test\"\n },\n \"mailingAddress\": {\n \"country\": \"CA\",\n \"address\": \"12766 227st\",\n \"addressLineTwo\": \"\",\n \"city\": \"MAPLE RIDGE\",\n \"province\": \"BC\",\n \"postalCode\": \"V2X 6K6\"\n }\n },\n \"secondaryContact\": {\n \"contactType\": \"INDIVIDUAL\",\n \"name\": {\n \"firstName\": \"The\",\n \"middleName\": \"Other\",\n \"lastName\": \"Guy\"\n },\n \"dateOfBirth\": \"1986-10-23\",\n \"details\": {\n \"preferredName\": \"Mouse\",\n \"phoneNumber\": \"604-888-8888\",\n \"extension\": \"\",\n \"faxNumber\": \"\",\n \"emailAddress\": \"test2@test.test\"\n },\n \"mailingAddress\": {\n \"country\": \"CA\",\n \"address\": \"12766 227st\",\n \"addressLineTwo\": \"\",\n \"city\": \"MAPLE RIDGE\",\n \"province\": \"BC\",\n \"postalCode\": \"V2X 6K6\"\n }\n },\n \"unitDetails\": {\n \"parcelIdentifier\": \"000-460-991\",\n \"businessLicense\": \"7777777\",\n \"businessLicenseExpiryDate\": \"2025-01-01\",\n \"propertyType\": \"SINGLE_FAMILY_HOME\",\n \"ownershipType\": \"OWN\",\n \"rentalUnitSpaceType\": \"ENTIRE_HOME\",\n \"hostResidence\": \"SAME_UNIT\",\n \"isUnitOnPrincipalResidenceProperty\": true,\n \"numberOfRoomsForRent\": 1\n },\n \"unitAddress\": {\n \"nickname\": \"My Rental Property\",\n \"country\": \"CA\",\n \"address\": \"12166 GREENWELL ST MAPLE RIDGE\",\n \"addressLineTwo\": \"\",\n \"city\": \"MAPLE RIDGE\",\n \"province\": \"BC\",\n \"postalCode\": \"V2X 7N1\"\n },\n \"listingDetails\": [\n {\n \"url\": \"https://www.airbnb.ca/rooms/26359027\"\n }\n ],\n \"principalResidence\": {\n \"isPrincipalResidence\": true,\n \"agreedToRentalAct\": true,\n \"nonPrincipalOption\": \"n/a\",\n \"specifiedServiceProvider\": \"n/a\",\n \"agreedToSubmit\": true\n }\n }\n}\n", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{api_url}}/applications", + "host": [ + "{{api_url}}" + ], + "path": [ + "applications" + ] + } + }, + "response": [] + }, { "name": "Update Application Payment Details", "request": { diff --git a/strr-api/tests/unit/resources/test_registration_applications.py b/strr-api/tests/unit/resources/test_registration_applications.py index aab43020a..50a669205 100644 --- a/strr-api/tests/unit/resources/test_registration_applications.py +++ b/strr-api/tests/unit/resources/test_registration_applications.py @@ -26,6 +26,9 @@ CREATE_STRATA_HOTEL_REGISTRATION_REQUEST = os.path.join( os.path.dirname(os.path.realpath(__file__)), "../../mocks/json/strata_hotel_registration.json" ) +CREATE_HOST_REGISTRATION_BUSINESS_AS_HOST = os.path.join( + os.path.dirname(os.path.realpath(__file__)), "../../mocks/json/business_as_host.json" +) ACCOUNT_ID = 1234 @@ -480,3 +483,46 @@ def test_create_strata_hotel_registration_application_bad_request(session, clien rv = client.post("/applications", json=json_data, headers=headers) assert HTTPStatus.BAD_REQUEST == rv.status_code + + +@patch("strr_api.services.strr_pay.create_invoice", return_value=MOCK_INVOICE_RESPONSE) +def test_create_registration_application_with_business_as_a_host(session, client, jwt): + with open(CREATE_HOST_REGISTRATION_BUSINESS_AS_HOST) as f: + json_data = json.load(f) + headers = create_header(jwt, [PUBLIC_USER], "Account-Id") + headers["Account-Id"] = ACCOUNT_ID + rv = client.post("/applications", json=json_data, headers=headers) + + assert HTTPStatus.CREATED == rv.status_code + + +@patch("strr_api.services.strr_pay.create_invoice", return_value=MOCK_INVOICE_RESPONSE) +def test_approve_registration_application_with_business_as_a_host(session, client, jwt): + with open(CREATE_HOST_REGISTRATION_BUSINESS_AS_HOST) as f: + headers = create_header(jwt, [PUBLIC_USER], "Account-Id") + headers["Account-Id"] = ACCOUNT_ID + json_data = json.load(f) + + rv = client.post("/applications", json=json_data, headers=headers) + response_json = rv.json + application_number = response_json.get("header").get("applicationNumber") + + application = Application.find_by_application_number(application_number=application_number) + application.payment_status = PaymentStatus.COMPLETED.value + application.save() + + staff_headers = create_header(jwt, [STRR_EXAMINER], "Account-Id") + status_update_request = {"status": Application.Status.FULL_REVIEW_APPROVED} + rv = client.put(f"/applications/{application_number}/status", json=status_update_request, headers=staff_headers) + assert HTTPStatus.OK == rv.status_code + response_json = rv.json + assert response_json.get("header").get("status") == Application.Status.FULL_REVIEW_APPROVED + assert response_json.get("header").get("reviewer").get("username") is not None + assert response_json.get("header").get("registrationId") is not None + assert response_json.get("header").get("registrationNumber") is not None + assert response_json.get("header").get("hostStatus") == "Approved" + assert response_json.get("header").get("examinerStatus") == "Approved – Examined" + assert response_json.get("header").get("examinerActions") == ApplicationSerializer.EXAMINER_ACTIONS.get( + Application.Status.FULL_REVIEW_APPROVED + ) + assert response_json.get("header").get("hostActions") == []