From 9eb106550ec8c8a1eb36b78c9da55796d9e7740a Mon Sep 17 00:00:00 2001 From: Kial Date: Thu, 26 Sep 2024 11:21:31 -0400 Subject: [PATCH] API - break out ownership and person in the db (#204) * API - break out ownership and person in the db Signed-off-by: Kial Jinnah * tweak Signed-off-by: Kial Jinnah * fix UI test Signed-off-by: Kial Jinnah * UI test / lint fixes Signed-off-by: Kial Jinnah * Updated pm tests, added statement id interest conncected people mapping Signed-off-by: Kial Jinnah * PR comment update Signed-off-by: Kial Jinnah --------- Signed-off-by: Kial Jinnah --- .../versions/20230821_191f8d1ddc48_.py | 10 + .../versions/20240924_0922_8d8279a86def_.py | 192 +++++++++++++++ btr-api/poetry.lock | 23 +- btr-api/pyproject.toml | 1 + btr-api/src/btr_api/config.py | 7 + btr-api/src/btr_api/models/__init__.py | 4 + btr-api/src/btr_api/models/base.py | 63 +++++ btr-api/src/btr_api/models/ownership.py | 103 ++++++++ btr-api/src/btr_api/models/person.py | 109 +++++++++ btr-api/src/btr_api/models/submission.py | 73 +++--- btr-api/src/btr_api/models/user.py | 88 +++---- btr-api/src/btr_api/resources/notify.py | 2 +- btr-api/src/btr_api/resources/submission.py | 72 ++---- btr-api/src/btr_api/services/auth.py | 31 ++- btr-api/src/btr_api/services/bor.py | 18 +- btr-api/src/btr_api/services/email.py | 8 +- btr-api/src/btr_api/services/entity.py | 17 ++ .../src/btr_api/services/registries_search.py | 4 +- btr-api/src/btr_api/services/submission.py | 145 +++++++++--- .../postman/btr-api.postman_collection.json | 201 ++++++++++------ btr-api/tests/unit/models/test_submission.py | 51 +++- btr-api/tests/unit/models/test_user.py | 28 +-- .../tests/unit/resources/test_submissions.py | 220 +++++++++++++----- .../tests/unit/services/test_submission.py | 31 ++- btr-api/tests/unit/utils/db_helpers.py | 48 ++++ .../cypress/e2e/pages/reviewConfirm.cy.ts | 5 +- .../services/file-significant-individual.ts | 5 +- .../tests/mocks/btrSubmissionExample.ts | 9 +- .../btr-bods/bods-to-si-schema-converters.ts | 3 +- .../utils/si-schema/definitions.ts | 1 + 30 files changed, 1219 insertions(+), 353 deletions(-) create mode 100644 btr-api/migrations/versions/20240924_0922_8d8279a86def_.py create mode 100644 btr-api/src/btr_api/models/base.py create mode 100644 btr-api/src/btr_api/models/ownership.py create mode 100644 btr-api/src/btr_api/models/person.py create mode 100644 btr-api/tests/unit/utils/db_helpers.py diff --git a/btr-api/migrations/versions/20230821_191f8d1ddc48_.py b/btr-api/migrations/versions/20230821_191f8d1ddc48_.py index f4ea9049..9354f3f6 100644 --- a/btr-api/migrations/versions/20230821_191f8d1ddc48_.py +++ b/btr-api/migrations/versions/20230821_191f8d1ddc48_.py @@ -73,3 +73,13 @@ def downgrade(): op.drop_table('users') # ### end Alembic commands ### + op.execute(f""" + DELETE + FROM pg_enum + WHERE enumtypid = (SELECT oid FROM pg_type WHERE typname='submissiontype') + """) + op.execute(f""" + DELETE + FROM pg_type + WHERE typname='submissiontype' + """) diff --git a/btr-api/migrations/versions/20240924_0922_8d8279a86def_.py b/btr-api/migrations/versions/20240924_0922_8d8279a86def_.py new file mode 100644 index 00000000..cf0ef1c0 --- /dev/null +++ b/btr-api/migrations/versions/20240924_0922_8d8279a86def_.py @@ -0,0 +1,192 @@ +"""empty message + +Revision ID: 8d8279a86def +Revises: 8850fb7d527b +Create Date: 2024-09-24 09:22:07.352266 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '8d8279a86def' +down_revision = '8850fb7d527b' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('person', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('statement_id', sa.Uuid(), nullable=False), + sa.Column('person_json', postgresql.JSONB(astext_type=sa.Text()), nullable=False), + sa.Column('birthdate', sa.Date(), nullable=True), + sa.Column('is_public', sa.Boolean(), nullable=False), + sa.Column('last_notification_type', sa.Enum('ADDING_ADULT', 'ADDING_MINOR', 'UPDATING_MINOR_PUBLIC', name='emailtype'), nullable=True), + sa.Column('last_notification_datetime', sa.DateTime(), nullable=True), + sa.Column('version', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('statement_id'), + sqlite_autoincrement=True + ) + op.create_table('person_history', + sa.Column('id', sa.Integer(), autoincrement=False, nullable=False), + sa.Column('statement_id', sa.Uuid(), autoincrement=False, nullable=False), + sa.Column('person_json', postgresql.JSONB(astext_type=sa.Text()), autoincrement=False, nullable=False), + sa.Column('birthdate', sa.Date(), autoincrement=False, nullable=True), + sa.Column('is_public', sa.Boolean(), autoincrement=False, nullable=False), + sa.Column('last_notification_type', sa.Enum('ADDING_ADULT', 'ADDING_MINOR', 'UPDATING_MINOR_PUBLIC', name='emailtype'), autoincrement=False, nullable=True), + sa.Column('last_notification_datetime', sa.DateTime(), autoincrement=False, nullable=True), + sa.Column('version', sa.Integer(), autoincrement=False, nullable=False), + sa.Column('changed', sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint('id', 'version'), + sqlite_autoincrement=True + ) + op.create_table('ownership', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('statement_id', sa.Uuid(), nullable=False), + sa.Column('ownership_json', postgresql.JSONB(astext_type=sa.Text()), nullable=False), + sa.Column('person_id', sa.Integer(), nullable=True), + sa.Column('submission_id', sa.Integer(), nullable=True), + sa.Column('version', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['person_id'], ['person.id'], ), + sa.ForeignKeyConstraint(['submission_id'], ['submission.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('statement_id'), + sqlite_autoincrement=True + ) + op.create_table('ownership_history', + sa.Column('id', sa.Integer(), autoincrement=False, nullable=False), + sa.Column('statement_id', sa.Uuid(), autoincrement=False, nullable=False), + sa.Column('ownership_json', postgresql.JSONB(astext_type=sa.Text()), autoincrement=False, nullable=False), + sa.Column('person_id', sa.Integer(), autoincrement=False, nullable=True), + sa.Column('submission_id', sa.Integer(), autoincrement=False, nullable=True), + sa.Column('version', sa.Integer(), autoincrement=False, nullable=False), + sa.Column('changed', sa.DateTime(), nullable=True), + sa.ForeignKeyConstraint(['person_id'], ['person.id'], ), + sa.ForeignKeyConstraint(['submission_id'], ['submission.id'], ), + sa.PrimaryKeyConstraint('id', 'version'), + sqlite_autoincrement=True + ) + with op.batch_alter_table('submission', schema=None) as batch_op: + batch_op.alter_column('type', + existing_type=postgresql.ENUM('other', 'standard', name='submissiontype'), + nullable=False) + batch_op.alter_column('submitted_datetime', + existing_type=postgresql.TIMESTAMP(timezone=True), + type_=sa.DateTime(), + nullable=False, + existing_server_default=sa.text('now()')) + batch_op.alter_column('submitted_payload', + existing_type=postgresql.JSONB(astext_type=sa.Text()), + nullable=False) + batch_op.alter_column('submitter_id', + existing_type=sa.INTEGER(), + nullable=False) + batch_op.drop_column('payload') + + with op.batch_alter_table('submission_history', schema=None) as batch_op: + batch_op.alter_column('type', + existing_type=postgresql.ENUM('other', 'standard', name='submissiontype'), + nullable=False, + autoincrement=False) + batch_op.alter_column('submitted_datetime', + existing_type=postgresql.TIMESTAMP(timezone=True), + type_=sa.DateTime(), + nullable=False, + autoincrement=False) + batch_op.alter_column('submitted_payload', + existing_type=postgresql.JSONB(astext_type=sa.Text()), + nullable=False, + autoincrement=False) + batch_op.alter_column('submitter_id', + existing_type=sa.INTEGER(), + nullable=False, + autoincrement=False) + batch_op.drop_column('payload') + + with op.batch_alter_table('users', schema=None) as batch_op: + batch_op.alter_column('username', + existing_type=sa.VARCHAR(length=1000), + nullable=False) + batch_op.alter_column('sub', + existing_type=sa.VARCHAR(length=36), + nullable=False) + batch_op.alter_column('iss', + existing_type=sa.VARCHAR(length=1024), + nullable=False) + batch_op.alter_column('idp_userid', + existing_type=sa.VARCHAR(length=256), + nullable=False) + batch_op.alter_column('creation_date', + existing_type=postgresql.TIMESTAMP(timezone=True), + type_=sa.DateTime(), + nullable=False) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('users', schema=None) as batch_op: + batch_op.alter_column('creation_date', + existing_type=sa.DateTime(), + type_=postgresql.TIMESTAMP(timezone=True), + nullable=True) + batch_op.alter_column('idp_userid', + existing_type=sa.VARCHAR(length=256), + nullable=True) + batch_op.alter_column('iss', + existing_type=sa.VARCHAR(length=1024), + nullable=True) + batch_op.alter_column('sub', + existing_type=sa.VARCHAR(length=36), + nullable=True) + batch_op.alter_column('username', + existing_type=sa.VARCHAR(length=1000), + nullable=True) + + with op.batch_alter_table('submission_history', schema=None) as batch_op: + batch_op.add_column(sa.Column('payload', postgresql.JSONB(astext_type=sa.Text()), autoincrement=False, nullable=True)) + batch_op.alter_column('submitter_id', + existing_type=sa.INTEGER(), + nullable=True, + autoincrement=False) + batch_op.alter_column('submitted_payload', + existing_type=postgresql.JSONB(astext_type=sa.Text()), + nullable=True, + autoincrement=False) + batch_op.alter_column('submitted_datetime', + existing_type=sa.DateTime(), + type_=postgresql.TIMESTAMP(timezone=True), + nullable=True, + autoincrement=False) + batch_op.alter_column('type', + existing_type=postgresql.ENUM('other', 'standard', name='submissiontype'), + nullable=True, + autoincrement=False) + + with op.batch_alter_table('submission', schema=None) as batch_op: + batch_op.add_column(sa.Column('payload', postgresql.JSONB(astext_type=sa.Text()), autoincrement=False, nullable=True)) + batch_op.alter_column('submitter_id', + existing_type=sa.INTEGER(), + nullable=True) + batch_op.alter_column('submitted_payload', + existing_type=postgresql.JSONB(astext_type=sa.Text()), + nullable=True) + batch_op.alter_column('submitted_datetime', + existing_type=sa.DateTime(), + type_=postgresql.TIMESTAMP(timezone=True), + nullable=True, + existing_server_default=sa.text('now()')) + batch_op.alter_column('type', + existing_type=postgresql.ENUM('other', 'standard', name='submissiontype'), + nullable=True) + + op.drop_table('ownership_history') + op.drop_table('ownership') + op.drop_table('person_history') + op.drop_table('person') + # ### end Alembic commands ### diff --git a/btr-api/poetry.lock b/btr-api/poetry.lock index 5b010b24..99d08687 100644 --- a/btr-api/poetry.lock +++ b/btr-api/poetry.lock @@ -95,13 +95,13 @@ files = [ [[package]] name = "cachelib" -version = "0.10.2" +version = "0.9.0" description = "A collection of cache libraries in the same API interface." optional = false python-versions = ">=3.7" files = [ - {file = "cachelib-0.10.2-py3-none-any.whl", hash = "sha256:42d49f2fad9310dd946d7be73d46776bcd4d5fde4f49ad210cfdd447fbdfc346"}, - {file = "cachelib-0.10.2.tar.gz", hash = "sha256:593faeee62a7c037d50fc835617a01b887503f972fb52b188ae7e50e9cb69740"}, + {file = "cachelib-0.9.0-py3-none-any.whl", hash = "sha256:811ceeb1209d2fe51cd2b62810bd1eccf70feba5c52641532498be5c675493b3"}, + {file = "cachelib-0.9.0.tar.gz", hash = "sha256:38222cc7c1b79a23606de5c2607f4925779e37cdcea1c2ad21b8bae94b5425a5"}, ] [[package]] @@ -456,6 +456,21 @@ Flask = ">=2.0" Jinja2 = ">=3.1" pytz = ">=2022.7" +[[package]] +name = "flask-caching" +version = "2.3.0" +description = "Adds caching support to Flask applications." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Flask_Caching-2.3.0-py3-none-any.whl", hash = "sha256:51771c75682e5abc1483b78b96d9131d7941dc669b073852edfa319dd4e29b6e"}, + {file = "flask_caching-2.3.0.tar.gz", hash = "sha256:d7e4ca64a33b49feb339fcdd17e6ba25f5e01168cf885e53790e885f83a4d2cf"}, +] + +[package.dependencies] +cachelib = ">=0.9.0,<0.10.0" +Flask = "*" + [[package]] name = "flask-cors" version = "4.0.0" @@ -1845,4 +1860,4 @@ watchdog = ["watchdog (>=2.3)"] [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "d68e71d00ea4a965a499c04ba3314fe594b9411221314c918ce2f348fbf6ec47" +content-hash = "322f9fa6ce0f2b4241ac7032ccd3efa170e54b588a9533924b1d91c617dcdbd4" diff --git a/btr-api/pyproject.toml b/btr-api/pyproject.toml index e29c25cb..bdaa1c4c 100644 --- a/btr-api/pyproject.toml +++ b/btr-api/pyproject.toml @@ -23,6 +23,7 @@ flask-jwt-oidc = "^0.3.0" gunicorn = "^21.2.0" jsonschema = {extras = ["format"], version = "^4.20.0"} pycountry = "^23.12.11" +flask-caching = "^2.3.0" [tool.poetry.group.test.dependencies] freezegun = "^1.2.2" diff --git a/btr-api/src/btr_api/config.py b/btr-api/src/btr_api/config.py index 2401c8e6..5f9b2743 100644 --- a/btr-api/src/btr_api/config.py +++ b/btr-api/src/btr_api/config.py @@ -125,6 +125,13 @@ class Config: # pylint: disable=too-few-public-methods LEGISLATIVE_TIMEZONE = os.getenv('LEGISLATIVE_TIMEZONE', 'America/Vancouver') + # Cache stuff + CACHE_TYPE = os.getenv('CACHE_TYPE', 'SimpleCache') + try: + CACHE_DEFAULT_TIMEOUT = int(os.getenv('CACHE_DEFAULT_TIMEOUT', '300')) + except (TypeError, ValueError): + CACHE_DEFAULT_TIMEOUT = 300 + class Production(Config): # pylint: disable=too-few-public-methods """Production class configuration that should override vars for production. diff --git a/btr-api/src/btr_api/models/__init__.py b/btr-api/src/btr_api/models/__init__.py index 657b5479..91f9619b 100644 --- a/btr-api/src/btr_api/models/__init__.py +++ b/btr-api/src/btr_api/models/__init__.py @@ -33,6 +33,8 @@ # POSSIBILITY OF SUCH DAMAGE. """This exports all of the models and schemas used by the application.""" from .db import db # noqa: I001 +from .ownership import Ownership +from .person import Person from .submission import Submission from .submission import SubmissionType from .user import User @@ -41,6 +43,8 @@ __all__ = ( "db", + "Ownership", + "Person", "Submission", "SubmissionType", "User", diff --git a/btr-api/src/btr_api/models/base.py b/btr-api/src/btr_api/models/base.py new file mode 100644 index 00000000..ca9d3e46 --- /dev/null +++ b/btr-api/src/btr_api/models/base.py @@ -0,0 +1,63 @@ +# Copyright © 2024 Province of British Columbia +# +# Licensed under the BSD 3 Clause License, (the "License"); +# you may not use this file except in compliance with the License. +# The template for the license can be found here +# https://opensource.org/license/bsd-3-clause/ +# +# Redistribution and use in source and binary forms, +# with or without modification, are permitted provided that the +# following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# 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. +"""Base Model.""" + +from .db import db + + +class Base(db.Model): + """Base model class for db tables.""" + + __abstract__ = True + + def save(self): + """Save and commit to the session.""" + db.session.add(self) + db.session.commit() + + def save_to_session(self): + """Save to the session. Does not commit.""" + db.session.add(self) + + return self + + @staticmethod + def commit(): + """Commit the session.""" + db.session.commit() + + @staticmethod + def rollback(): + """RollBack the session.""" + db.session.rollback() diff --git a/btr-api/src/btr_api/models/ownership.py b/btr-api/src/btr_api/models/ownership.py new file mode 100644 index 00000000..e994bc0b --- /dev/null +++ b/btr-api/src/btr_api/models/ownership.py @@ -0,0 +1,103 @@ +# Copyright © 2024 Province of British Columbia +# +# Licensed under the BSD 3 Clause License, (the "License"); +# you may not use this file except in compliance with the License. +# The template for the license can be found here +# https://opensource.org/license/bsd-3-clause/ +# +# Redistribution and use in source and binary forms, +# with or without modification, are permitted provided that the +# following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# 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. +"""Manages ownership data.""" +from __future__ import annotations + +import uuid +from typing import TYPE_CHECKING + +from sqlalchemy import Column, ForeignKey, event +from sqlalchemy.dialects.postgresql import JSONB +from sqlalchemy.orm import Mapped, mapped_column, relationship +from sql_versioning import Versioned + +from .base import Base + +if TYPE_CHECKING: + # https://mypy.readthedocs.io/en/stable/runtime_troubles.html#import-cycles + from .person import Person + from .submission import Submission + + +class Ownership(Versioned, Base): + """Stores ownership statement data.""" + + __tablename__ = 'ownership' + + id: Mapped[int] = mapped_column(primary_key=True) + statement_id: Mapped[uuid.UUID] = mapped_column(nullable=False, unique=True) + ownership_json = Column(JSONB, nullable=False) + + # Relationships + person_id: Mapped[int] = mapped_column(ForeignKey('person.id'), nullable=True) + person: Mapped['Person'] = relationship(back_populates='ownerships') + + submission_id: Mapped[int] = mapped_column(ForeignKey('submission.id'), nullable=True) + submission: Mapped['Submission'] = relationship(back_populates='ownership_statements') + + @classmethod + def find_by_id(cls, ownership_id: int) -> Ownership | None: + """Return the ownership by id.""" + return cls.query.filter_by(id=ownership_id).one_or_none() + + @classmethod + def find_by_statement_id(cls, statement_id: str) -> Ownership | None: + """Return the ownership by statement.""" + return cls.query.filter_by(statement_id=statement_id).one_or_none() + + @classmethod + def find_all_by_submission_id(cls, submission_id: int) -> list[Ownership]: + """Return the ownerships by submission_id.""" + return cls.query.filter_by(submission_id=submission_id).all() + + @classmethod + def find_all_by_person_id(cls, person_id: int) -> list[Ownership]: + """Return the ownerships by person_id.""" + return cls.query.filter_by(person_id=person_id).all() + + +@event.listens_for(Ownership, 'before_insert') +def receive_before_insert(mapper, connection, target: Ownership): # pylint: disable=unused-argument + """Set the statement_id and describedByPersonStatement within the ownership_json.""" + target.ownership_json['statementID'] = str(target.statement_id) + target.ownership_json['interestedParty']['describedByPersonStatement'] = str(target.person.statement_id) + + for interest in target.ownership_json.get('interests', []): + for individual in interest.get('connectedIndividuals', []): + # update the mapping to the generated statement id + if ( + individual.get('uuid') and + (matching_statement_id := target.person.find_statement_id_by_uuid(individual['uuid'])) + ): + individual['uuid'] = matching_statement_id diff --git a/btr-api/src/btr_api/models/person.py b/btr-api/src/btr_api/models/person.py new file mode 100644 index 00000000..8eba1b75 --- /dev/null +++ b/btr-api/src/btr_api/models/person.py @@ -0,0 +1,109 @@ +# Copyright © 2024 Province of British Columbia +# +# Licensed under the BSD 3 Clause License, (the "License"); +# you may not use this file except in compliance with the License. +# The template for the license can be found here +# https://opensource.org/license/bsd-3-clause/ +# +# Redistribution and use in source and binary forms, +# with or without modification, are permitted provided that the +# following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# 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. +"""Manages person data.""" +from __future__ import annotations + +import uuid +from datetime import date, datetime +from typing import TYPE_CHECKING + +from dateutil.relativedelta import relativedelta +from sqlalchemy import Column, event +from sqlalchemy.dialects.postgresql import JSONB +from sqlalchemy.orm import Mapped, mapped_column, relationship +from sql_versioning import Versioned + +from btr_api.enums import EmailType +from btr_api.utils.legislation_datetime import LegislationDatetime + +from .base import Base + +if TYPE_CHECKING: + # https://mypy.readthedocs.io/en/stable/runtime_troubles.html#import-cycles + from .ownership import Ownership + + +class Person(Versioned, Base): + """Stores person statement data.""" + + __tablename__ = 'person' + + id: Mapped[int] = mapped_column(primary_key=True) + statement_id: Mapped[uuid.UUID] = mapped_column(nullable=False, unique=True) + person_json = Column(JSONB, nullable=False) + + birthdate: Mapped[date] = mapped_column(nullable=True) + is_public: Mapped[bool] = mapped_column(default=True) + last_notification_type: Mapped[EmailType] = mapped_column(nullable=True) + last_notification_datetime: Mapped[datetime] = mapped_column(nullable=True) + + # relationships + ownerships: Mapped[list['Ownership']] = relationship(back_populates='person') + + @property + def is_minor(self): + """Return True if the person is under 19 (calculation based off birthdate).""" + if self.birthdate: + minor_threshold = LegislationDatetime.now() - relativedelta(years=19) + return minor_threshold.date() < self.birthdate + # if birthdate is not entered then the person is treated as an adult + return False + + @classmethod + def find_by_id(cls, person_id: int) -> Person | None: + """Return the person by id.""" + return cls.query.filter_by(id=person_id).one_or_none() + + @classmethod + def find_by_statement_id(cls, statement_id: str) -> Person | None: + """Return the person by statement.""" + return cls.query.filter_by(statement_id=statement_id).one_or_none() + + @classmethod + def find_statement_id_by_uuid(cls, orig_uuid: str) -> str: + """Return the generated statement id by the uuid in the person_json.""" + # NOTE: if this becomes slow add a jsonb index or pass in the ownership id as well + person: Person = cls.query.filter(cls.person_json['uuid'].astext == orig_uuid).one_or_none() + return str(person.statement_id) if person else None + + +@event.listens_for(Person, 'before_insert') +def receive_before_insert(mapper, connection, target: Person): # pylint: disable=unused-argument + """Set the statement_id within the person_json and the birthdate.""" + target.person_json['statementID'] = str(target.statement_id) + + if birthdate := target.person_json.get('birthDate'): + target.birthdate = LegislationDatetime.as_legislation_timezone_from_date_str(birthdate).date() + # check if minor here / flip is public if necessary + target.is_public = target.is_minor diff --git a/btr-api/src/btr_api/models/submission.py b/btr-api/src/btr_api/models/submission.py index 6e3f9dfc..4d84caee 100644 --- a/btr-api/src/btr_api/models/submission.py +++ b/btr-api/src/btr_api/models/submission.py @@ -31,18 +31,26 @@ # 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. -"""Sample submission class.""" +"""Manages submission data for the BTR filing.""" from __future__ import annotations -from sqlalchemy import desc, func +from datetime import date, datetime +from typing import TYPE_CHECKING + +from sqlalchemy import Column, ForeignKey, desc, event from sqlalchemy.dialects.postgresql import JSONB -from sqlalchemy.orm import backref +from sqlalchemy.orm import Mapped, mapped_column, relationship from sql_versioning import Versioned -from .db import db +from .base import Base from ..common.enum import auto from ..common.enum import BaseEnum +if TYPE_CHECKING: + # https://mypy.readthedocs.io/en/stable/runtime_troubles.html#import-cycles + from .ownership import Ownership + from .user import User + class SubmissionType(BaseEnum): """Enum of the roles used across the domain.""" @@ -51,38 +59,30 @@ class SubmissionType(BaseEnum): standard = auto() # pylint: disable=invalid-name -class Submission(Versioned, db.Model): +class Submission(Versioned, Base): """Stores a submission of JSON data.""" - __tablename__ = "submission" + __tablename__ = 'submission' - id = db.Column(db.Integer, primary_key=True) - type = db.Column(db.Enum(SubmissionType), default=SubmissionType.other) - effective_date = db.Column(db.Date(), nullable=True) - submitted_datetime = ( - db.Column("submitted_datetime", db.DateTime(timezone=True), - server_default=func.now())) # pylint:disable=not-callable - payload = db.Column("payload", JSONB) - business_identifier = db.Column(db.String(255), nullable=False, unique=True, index=True) + id: Mapped[int] = mapped_column(primary_key=True) + type: Mapped[SubmissionType] = mapped_column(default=SubmissionType.other) + effective_date: Mapped[date] = mapped_column(nullable=True) + submitted_datetime: Mapped[datetime] = mapped_column() + submitted_payload = Column(JSONB, nullable=False) + business_identifier: Mapped[str] = mapped_column(nullable=False, unique=True, index=True) # maps to invoice id created by the pay-api (used for getting receipt) - invoice_id = db.Column(db.Integer, nullable=True) - submitted_payload = db.Column("submitted_payload", JSONB) + invoice_id: Mapped[int] = mapped_column(nullable=True) # Relationships - submitter_id = db.Column('submitter_id', db.Integer, db.ForeignKey('users.id')) - - submitter = db.relationship('User', - backref=backref('filing_submitter', uselist=False), - foreign_keys=[submitter_id]) + ownership_statements: Mapped[list['Ownership']] = relationship(back_populates='submission') - def save(self): - """Save and commit immediately.""" - db.session.add(self) - db.session.commit() + submitter_id: Mapped[int] = mapped_column(ForeignKey('users.id')) + submitter: Mapped['User'] = relationship(back_populates='submissions') - def save_to_session(self): - """Save toThe session, do not commit immediately.""" - db.session.add(self) + @property + def person_statements_json(self): + """Return the person statements json linked through this submission's ownership statements.""" + return [ownership.person.person_json for ownership in self.ownership_statements] @classmethod def find_by_id(cls, submission_id) -> Submission | None: @@ -101,6 +101,16 @@ def get_filtered_submissions(cls): return query.all() +@event.listens_for(Submission, 'before_insert') +@event.listens_for(Submission, 'before_update') +def receive_before_change(mapper, connection, target: Submission): # pylint: disable=unused-argument + """Update the submitted value, effective date, and business identifier.""" + target.submitted_datetime = datetime.now() + target.business_identifier = target.submitted_payload.get('businessIdentifier') + if effective_date := target.submitted_payload.get('effectiveDate'): + target.effective_date = date.fromisoformat(effective_date) + + class SubmissionSerializer: """Serializer for submissions. Can convert to dict, string from submission model. """ @@ -117,6 +127,11 @@ def to_dict(submission: Submission) -> dict: 'id': submission.id, 'type': submission.type.value, 'submittedDatetime': submission.submitted_datetime.isoformat(), - 'payload': submission.payload, 'submitterId': submission.submitter_id, + 'payload': { + **submission.submitted_payload, + 'ownershipOrControlStatements': [ + ownership.ownership_json for ownership in submission.ownership_statements], + 'personStatements': submission.person_statements_json + }, } diff --git a/btr-api/src/btr_api/models/user.py b/btr-api/src/btr_api/models/user.py index c1f56e31..24d77abc 100644 --- a/btr-api/src/btr_api/models/user.py +++ b/btr-api/src/btr_api/models/user.py @@ -1,6 +1,6 @@ # Copyright © 2023 Province of British Columbia # -# Licensed under the BSD 3 Clause License, (the "License"); +# Licensed under the BSD 3 Clause License, (the 'License'); # you may not use this file except in compliance with the License. # The template for the license can be found here # https://opensource.org/license/bsd-3-clause/ @@ -38,12 +38,20 @@ """ from __future__ import annotations +from datetime import datetime +from typing import TYPE_CHECKING + from flask import current_app +from sqlalchemy.orm import Mapped, mapped_column, relationship -from .db import db +from .base import Base from ..common.enum import auto from ..common.enum import BaseEnum +if TYPE_CHECKING: + # https://mypy.readthedocs.io/en/stable/runtime_troubles.html#import-cycles + from .submission import Submission + class UserRoles(BaseEnum): """Enum of the roles used across the domain.""" @@ -59,22 +67,24 @@ class UserRoles(BaseEnum): # pylint: enable=invalid-name -class User(db.Model): +class User(Base): """Used to hold the audit information for a User of this service.""" - __tablename__ = "users" - - id = db.Column(db.Integer, primary_key=True) - username = db.Column(db.String(1000), index=True) - firstname = db.Column(db.String(1000)) - lastname = db.Column(db.String(1000)) - middlename = db.Column(db.String(1000)) - email = db.Column(db.String(1024)) - sub = db.Column(db.String(36), unique=True) - iss = db.Column(db.String(1024)) - idp_userid = db.Column(db.String(256), index=True) - login_source = db.Column(db.String(200), nullable=True) - creation_date = db.Column(db.DateTime(timezone=True)) + __tablename__ = 'users' + + id: Mapped[int] = mapped_column(primary_key=True) + username: Mapped[str] = mapped_column(index=True) + firstname: Mapped[str] = mapped_column(nullable=True) + lastname: Mapped[str] = mapped_column(nullable=True) + middlename: Mapped[str] = mapped_column(nullable=True) + email: Mapped[str] = mapped_column(nullable=True) + sub: Mapped[str] = mapped_column(unique=True) + iss: Mapped[str] = mapped_column() + idp_userid: Mapped[str] = mapped_column(index=True) + login_source: Mapped[str] = mapped_column(nullable=True) + creation_date: Mapped[datetime] = mapped_column(default=datetime.now) + # relationships + submissions: Mapped[list['Submission']] = relationship(back_populates='submitter') @property def display_name(self): @@ -83,16 +93,16 @@ def display_name(self): If there is actual name info, return that; otherwise username. """ if self.firstname or self.lastname or self.middlename: - return " ".join(filter(None, [self.firstname, self.middlename, self.lastname])).strip() + return ' '.join(filter(None, [self.firstname, self.middlename, self.lastname])).strip() # parse off idir\ or @idir - if self.username[:4] == "idir": + if self.username[:4] == 'idir': return self.username[5:] - if self.username[-4:] == "idir": + if self.username[-4:] == 'idir': return self.username[:-5] # do not show services card usernames - if self.username[:4] == "bcsc": + if self.username[:4] == 'bcsc': return None return self.username if self.username else None @@ -105,7 +115,7 @@ def find_by_id(cls, submitter_id: int = None) -> User | None: @classmethod def find_by_jwt_token(cls, token: dict) -> User | None: """Return a User if they exist and match the provided JWT.""" - if user_id := token.get("idp_userid"): + if user_id := token.get('idp_userid'): return cls.query.filter_by(idp_userid=user_id).one_or_none() return None @@ -119,15 +129,15 @@ def create_from_jwt_token(cls, token: dict) -> User | None: if token: conf = current_app.config user = User( - username=token.get(conf.get("JWT_OIDC_USERNAME"), None), - firstname=token.get(conf.get("JWT_OIDC_FIRSTNAME"), None), - lastname=token.get(conf.get("JWT_OIDC_LASTNAME"), None), - iss=token["iss"], - sub=token["sub"], - idp_userid=token["idp_userid"], - login_source=token["loginSource"], + username=token.get(conf.get('JWT_OIDC_USERNAME'), None), + firstname=token.get(conf.get('JWT_OIDC_FIRSTNAME'), None), + lastname=token.get(conf.get('JWT_OIDC_LASTNAME'), None), + iss=token['iss'], + sub=token['sub'], + idp_userid=token['idp_userid'], + login_source=token['loginSource'], ) - current_app.logger.debug(f"Creating user JWT:{token}; User:{user}") + current_app.logger.debug(f'Creating user JWT:{token}; User:{user}') user.save() return user return None @@ -138,18 +148,18 @@ def get_or_create_user_by_jwt(cls, jwt_oidc_token) -> User: # GET existing or CREATE new user based on the JWT info try: user = User.find_by_jwt_token(jwt_oidc_token) - current_app.logger.debug(f"finding user: {jwt_oidc_token}") + current_app.logger.debug(f'finding user: {jwt_oidc_token}') if not user: - current_app.logger.debug(f"didnt find user, create new user:{jwt_oidc_token}") + current_app.logger.debug(f'didnt find user, create new user:{jwt_oidc_token}') user = User.create_from_jwt_token(jwt_oidc_token) return user except Exception as err: current_app.logger.error(err.with_traceback(None)) raise Exception( # pylint: disable=broad-exception-raised - "unable_to_get_or_create_user", - '{"code": "unable_to_get_or_create_user",' - '"description": "Unable to get or create user from the JWT"}', + 'unable_to_get_or_create_user', + "{'code': 'unable_to_get_or_create_user'," + "'description': 'Unable to get or create user from the JWT'}", ) from err @classmethod @@ -161,13 +171,3 @@ def find_by_username(cls, username) -> User | None: def find_by_sub(cls, sub) -> User | None: """Return a User based on the unique sub field.""" return cls.query.filter_by(sub=sub).one_or_none() - - def save(self): - """Store the User into the local cache.""" - db.session.add(self) - db.session.commit() - - def delete(self): - """Cannot delete User records.""" - return self - # need to intercept the ORM and stop Users from being deleted diff --git a/btr-api/src/btr_api/resources/notify.py b/btr-api/src/btr_api/resources/notify.py index b78c8b96..8422c28c 100644 --- a/btr-api/src/btr_api/resources/notify.py +++ b/btr-api/src/btr_api/resources/notify.py @@ -53,7 +53,7 @@ def registers(): # pylint: disable=redefined-builtin business = btr_entity.get_entity_info(None, business_identifier, token).json() delivery_address = btr_entity.get_entity_info( None, f'{business_identifier}/addresses?addressType=deliveryAddress', token).json() - business_contact = btr_auth.get_business_contact(business_identifier, token) + business_contact = btr_auth.get_business_contact(token, business_identifier) business_info = {**business, **delivery_address, 'contact': {**business_contact}} submission = Submission.find_by_business_identifier(business_identifier) diff --git a/btr-api/src/btr_api/resources/submission.py b/btr-api/src/btr_api/resources/submission.py index e06f9990..1f1739fc 100644 --- a/btr-api/src/btr_api/resources/submission.py +++ b/btr-api/src/btr_api/resources/submission.py @@ -47,22 +47,23 @@ from flask_cors import cross_origin from btr_api.common.auth import jwt -from btr_api.exceptions import AuthException, BusinessException, ExternalServiceException +from btr_api.exceptions import AuthException, ExternalServiceException from btr_api.exceptions import error_request_response from btr_api.exceptions import exception_response from btr_api.models import Submission as SubmissionModel from btr_api.models import User as UserModel from btr_api.models.submission import SubmissionSerializer -from btr_api.services import btr_auth, btr_bor, btr_email, btr_entity, btr_pay, btr_reg_search +from btr_api.services import btr_auth, btr_bor, btr_entity, btr_pay, btr_reg_search from btr_api.services import SchemaService from btr_api.services import SubmissionService from btr_api.services.validator import validate_entity -from btr_api.utils import redact_information, deep_spread +from btr_api.utils import redact_information bp = Blueprint('submission', __name__) @bp.route('/', methods=('GET',)) +@jwt.requires_auth def registers(id: int | None = None): # pylint: disable=redefined-builtin """Get the submissions, or specific submission by id.""" try: @@ -82,6 +83,7 @@ def registers(id: int | None = None): # pylint: disable=redefined-builtin @bp.route('/entity/', methods=('GET',)) +@jwt.requires_auth def get_entity_submission(business_identifier: str): """Get the current submission for specified business identifier.""" @@ -137,8 +139,6 @@ def create_register(): # create submission submission = SubmissionService.create_submission(json_input, user.id) - # save before attempting invoice creation so that we can log the id for ops if there's an error - submission.save_to_session() try: # NOTE: this will be moved out of this api once lear filings are linked # create invoice in pay system @@ -166,30 +166,12 @@ def create_register(): current_app.logger.info(err.error) current_app.logger.error('Error updating search record for submission: %s', submission.id) - try: - # NOTE: will be moved to person or ownership table @event.listens_for after_insert #23291 - # send emails out to newly added people - token = btr_auth.get_bearer_token() - delivery_address = btr_entity.get_entity_info( - None, f'{business_identifier}/addresses?addressType=deliveryAddress', token).json() - business_contact = btr_auth.get_business_contact(business_identifier, token) - business_info = {**entity, **delivery_address, 'contact': {**business_contact}} - - for person in submission.payload['personStatements']: - # NOTE: unable to confirm information people may not have an email entered - if person.get('email'): - btr_email.send_added_to_btr_email(person, business_info, submission.effective_date, token) - - except (BusinessException, ExternalServiceException) as err: - # Log error and continue to return successfully (does NOT block the submission) - current_app.logger.info(err.error) - current_app.logger.error('Error sending emails for business: %s', business_identifier) - - return jsonify(id=submission.id), HTTPStatus.CREATED + return jsonify(SubmissionSerializer.to_dict(submission)), HTTPStatus.CREATED except AuthException as aex: return exception_response(aex) except Exception as exception: # noqa: B902 + current_app.logger.error(exception.with_traceback(None)) return exception_response(exception) @@ -222,20 +204,19 @@ def update_submission(sub_id: int): return error_request_response('Invalid entity', HTTPStatus.FORBIDDEN, entity_errors) submitted_json = request.get_json() - prev_submission_dict = SubmissionSerializer.to_dict(submission) - new_payload = deep_spread(prev_submission_dict['payload'], submitted_json) + submission = SubmissionService.update_submission(submission, + submitted_json, + user.id) - # validate payload; TODO: implement business rules validations + submission.save_to_session() + new_full_submission = SubmissionSerializer.to_dict(submission)['payload'] + # validate resulting full submission schema_name = 'btr-filing.schema.json' schema_service = SchemaService() - [valid, errors] = schema_service.validate(schema_name, new_payload) + [valid, errors] = schema_service.validate(schema_name, new_full_submission) if not valid: return error_request_response('Invalid schema', HTTPStatus.BAD_REQUEST, errors) - submission = SubmissionService.update_submission(submission, new_payload, user.id, submitted_json) - - submission.save() - try: # NOTE: this will be moved out of this api once lear filings are linked # create invoice in pay system @@ -246,6 +227,8 @@ def update_submission(sub_id: int): current_app.logger.info(err.error) current_app.logger.error('Error creating invoice for submission: %s', submission.id) + submission.save() + try: # NOTE: this will be moved out of this api once lear filings are linked # update record in BOR (search) @@ -262,27 +245,7 @@ def update_submission(sub_id: int): current_app.logger.info(err.error) current_app.logger.error('Error updating search records for submission: %s', submission.id) - try: - # NOTE: will be moved to person or ownership table @event.listens_for after_insert #23291 - # send emails out to newly added people - token = btr_auth.get_bearer_token() - delivery_address = btr_entity.get_entity_info( - None, f'{business_identifier}/addresses?addressType=deliveryAddress', token).json() - business_contact = btr_auth.get_business_contact(business_identifier, token) - business_info = {**entity, **delivery_address, 'contact': {**business_contact}} - - existing_people = [person['uuid'] for person in prev_submission_dict['payload']['personStatements']] - for person in submission.payload['personStatements']: - # NOTE: unable to confirm information people may not have an email entered - if person['uuid'] not in existing_people and person.get('email'): - btr_email.send_added_to_btr_email(person, business_info, submission.effective_date, token) - - except (BusinessException, ExternalServiceException) as err: - # Log error and continue to return successfully (does NOT block the submission) - current_app.logger.info(err.error) - current_app.logger.error('Error sending emails for business: %s', business_identifier) - - return jsonify(redact_information(SubmissionSerializer.to_dict(submission), + return jsonify(redact_information(new_full_submission, btr_auth.get_user_type())), HTTPStatus.OK return {}, HTTPStatus.NOT_FOUND @@ -290,4 +253,5 @@ def update_submission(sub_id: int): except AuthException as aex: return exception_response(aex) except Exception as exception: # noqa: B902 + current_app.logger.error(exception.with_traceback(None)) return exception_response(exception) diff --git a/btr-api/src/btr_api/services/auth.py b/btr-api/src/btr_api/services/auth.py index e81668a7..8adfd0e8 100644 --- a/btr-api/src/btr_api/services/auth.py +++ b/btr-api/src/btr_api/services/auth.py @@ -33,9 +33,11 @@ # POSSIBILITY OF SUCH DAMAGE. """Manages auth service interactions.""" from http import HTTPStatus -from flask import Flask + import requests -from requests import exceptions +from requests import Request, exceptions +from flask import Flask +from flask_caching import Cache from btr_api.exceptions import AuthException from btr_api.exceptions import ExternalServiceException @@ -43,6 +45,9 @@ from btr_api.enums import UserType +auth_cache = Cache() + + class AuthService: """Provides utility functions for connecting with the BC Registries auth-api and SSO service.""" @@ -68,6 +73,16 @@ def init_app(self, app: Flask): self.sso_svc_timeout = app.config.get('SSO_SVC_TIMEOUT', 20) self.svc_acc_id = app.config.get('SVC_ACC_CLIENT_ID') self.svc_acc_secret = app.config.get('SVC_ACC_CLIENT_SECRET') + auth_cache.init_app(app) + + def get_cache_key(self, auth_arg: str | Request, key: str | int): + """Return the cache key for the given args.""" + if isinstance(auth_arg, Request): + try: + auth_arg = self.get_authorization_header(auth_arg) + except AuthException: + return None + return auth_arg + str(key) def get_user_type(self): """ @@ -93,6 +108,7 @@ def get_user_type(self): self.app.logger.info('Get User Type: ' + UserType.USER_PUBLIC) return UserType.USER_PUBLIC + @auth_cache.cached(timeout=300) def get_bearer_token(self): """Get a valid Bearer token for the service to use.""" data = 'grant_type=client_credentials' @@ -120,7 +136,7 @@ def get_bearer_token(self): [{'message': 'Unable to get service account token.', 'reason': err.with_traceback(None)}], ) from err - def get_authorization_header(self, request) -> str: + def get_authorization_header(self, request: Request) -> str: """Gets authorization header from request.""" authorization_header = request.headers.get('Authorization', None) if not authorization_header: @@ -130,7 +146,7 @@ def get_authorization_header(self, request) -> str: return authorization_header - # TODO: Add caching + @auth_cache.cached(timeout=600, make_cache_key=get_cache_key, cache_none=False) def product_authorizations(self, request, account_id: str) -> None: """Get the products associated with the user and account_id.""" if not account_id: @@ -159,8 +175,8 @@ def product_authorizations(self, request, account_id: str) -> None: self.app.logger.debug(err) return - # TODO: Add caching - def is_authorized(self, request, business_identifier: str) -> bool: + @auth_cache.cached(timeout=600, make_cache_key=get_cache_key, cache_none=False) + def is_authorized(self, request: Request, business_identifier: str) -> bool: """Authorize the user for access to the service.""" try: auth_token = self.get_authorization_header(request) @@ -194,7 +210,8 @@ def is_authorized(self, request, business_identifier: str) -> bool: self.app.logger.debug('Generic Auth verification failure:', repr(err)) raise ExternalServiceException(error=repr(err), status_code=HTTPStatus.SERVICE_UNAVAILABLE) from err - def get_business_contact(self, business_identifier: str, token: str) -> bool: + @auth_cache.cached(timeout=600, make_cache_key=get_cache_key) + def get_business_contact(self, token: str, business_identifier: str) -> bool: """Get the business contact information from auth.""" try: headers = {'Authorization': 'Bearer ' + token} diff --git a/btr-api/src/btr_api/services/bor.py b/btr-api/src/btr_api/services/bor.py index 2ed36e54..ef3aaeab 100644 --- a/btr-api/src/btr_api/services/bor.py +++ b/btr-api/src/btr_api/services/bor.py @@ -65,19 +65,21 @@ def update_owners(self, submission: Submission, business: dict, token: str) -> r try: # collect current parties parties = {} - for person in submission.payload.get('personStatements', []): + for person in submission.person_statements_json: person_id = person['statementID'] parties[person_id] = person # combine ownership details and parties owners = [] - for ownership_info in submission.payload.get('ownershipOrControlStatements', []): - party_id = ownership_info['interestedParty']['describedByPersonStatement'] - ownership_info['interestedParty'] = { - 'describedByPersonStatement': party_id, - **parties[party_id] - } - owners.append(ownership_info) + for ownership_info in submission.ownership_statements: + party_id = ownership_info.ownership_json['interestedParty']['describedByPersonStatement'] + owners.append({ + **ownership_info.ownership_json, + 'interestedParty': { + 'describedByPersonStatement': party_id, + **parties[party_id] + } + }) # make update call to bor with headers + payload payload = {**business, 'owners': owners} diff --git a/btr-api/src/btr_api/services/email.py b/btr-api/src/btr_api/services/email.py index b6c69ea6..826409d3 100644 --- a/btr-api/src/btr_api/services/email.py +++ b/btr-api/src/btr_api/services/email.py @@ -34,7 +34,7 @@ """ This module contains the services necessary for handling email notifications. """ -from datetime import date, timedelta +from datetime import timedelta from http import HTTPStatus from pathlib import Path @@ -115,10 +115,10 @@ def _compose_updating_minor_public_email(self, person: dict, business_info: dict 'content': {'subject': subject, 'body': f'{html_out}'}, } - def _compose_added_email(self, person: dict, business_info: dict, effective_date: date) -> dict: + def _compose_added_email(self, person: dict, business_info: dict, effective_date: str) -> dict: """Return email data for EmailType.ADDING_ADULT or EmailType.ADDING_MINOR""" email_type = EmailType.ADDING_ADULT - effective_datetime = LegislationDatetime.as_legislation_timezone_from_date(effective_date) + effective_datetime = LegislationDatetime.as_legislation_timezone_from_date_str(effective_date) # dates start_date_label = 'Registered Date' start_date_desc = 'registration date' @@ -237,7 +237,7 @@ def _substitute_template_parts(self, template_code: str) -> str: def send_added_to_btr_email(self, person_statement: dict, business_info: dict, - effective_date: date, + effective_date: str, token: str): """Send 'added person to btr' email via notify api.""" try: diff --git a/btr-api/src/btr_api/services/entity.py b/btr-api/src/btr_api/services/entity.py index 1a48cb2b..126a11a3 100644 --- a/btr-api/src/btr_api/services/entity.py +++ b/btr-api/src/btr_api/services/entity.py @@ -36,11 +36,15 @@ import requests from flask import Flask +from flask_caching import Cache from flask_jwt_oidc import JwtManager from btr_api.exceptions import ExternalServiceException +entity_cache = Cache() + + class EntityService: """ A class that provides utility functions for connecting with the BC Registries legal api. @@ -49,12 +53,25 @@ class EntityService: svc_url: str = None timeout: int = None + def __init__(self, app: Flask = None): + """Initialize the entity service.""" + if app: + self.init_app(app) + def init_app(self, app: Flask): """Initialize app dependent variables.""" self.app = app self.svc_url = app.config.get('LEGAL_SVC_URL') self.timeout = app.config.get('LEGAL_SVC_TIMEOUT', 20) + entity_cache.init_app(app) + + def get_cache_key(self, jwt: JwtManager, path: str, token: str): + """Return the cache key for the given args.""" + if not token: + token = jwt.get_token_auth_header() + return token + path + @entity_cache.cached(timeout=600, make_cache_key=get_cache_key) def get_entity_info(self, user_jwt: JwtManager, path: str, token: str = None) -> requests.Response: """Get the entity info for the given path. diff --git a/btr-api/src/btr_api/services/registries_search.py b/btr-api/src/btr_api/services/registries_search.py index 768804ac..3d2755df 100644 --- a/btr-api/src/btr_api/services/registries_search.py +++ b/btr-api/src/btr_api/services/registries_search.py @@ -69,7 +69,7 @@ def update_business(self, submission: Submission, business: dict, token: str) -> business_identifier = business['business']['identifier'] # collect current parties parties = [] - for person in submission.payload.get('personStatements', []): + for person in submission.person_statements_json: party_name = '' for name in person.get('names'): if name.get('type') == 'individual': # expecting this to be 'individual' or 'alternative' @@ -80,7 +80,7 @@ def update_business(self, submission: Submission, business: dict, token: str) -> self.app.logger.error('Error parsing SI name for %s', business_identifier) parties.append({ - 'id': f"{business_identifier}_{person['uuid']}", + 'id': f"{business_identifier}_{person['statementID']}", 'partyName': party_name, 'partyRoles': ['significant individual'], 'partyType': 'person' diff --git a/btr-api/src/btr_api/services/submission.py b/btr-api/src/btr_api/services/submission.py index 6ffe987d..1b2c6d2d 100644 --- a/btr-api/src/btr_api/services/submission.py +++ b/btr-api/src/btr_api/services/submission.py @@ -39,11 +39,21 @@ which accepts a dictionary as an input and returns a SubmissionModel object. The individual services can be invoked as per the requirements. - """ -from datetime import date +from copy import deepcopy +from http import HTTPStatus +import uuid + +from flask import current_app + +from btr_api.common.auth import jwt +from btr_api.exceptions import BusinessException, ExternalServiceException +from btr_api.models import Ownership, Person, Submission as SubmissionModel +from btr_api.utils import deep_spread -from btr_api.models import Submission as SubmissionModel +from .auth import AuthService +from .email import EmailService +from .entity import EntityService class SubmissionService: # pylint: disable=too-few-public-methods @@ -52,15 +62,61 @@ class SubmissionService: # pylint: disable=too-few-public-methods Creates a submission model based on the given submission dictionary. The submission dictionary should contain the necessary information for creating a submission. - """ + @staticmethod + def _get_linked_ownership_stmnt(person_stmnt_id: str, ownership_stmnts: list[dict]): + """Return the linked ownership statement. Raises a BusinessException if a linked statement is not found.""" + for ownership in ownership_stmnts: + if ownership['interestedParty']['describedByPersonStatement'] == person_stmnt_id: + return ownership + + raise BusinessException( + 'Invalid payload. All submitted person statements must be linked to an ownership statement.', + f'Person statement id: {person_stmnt_id}', + HTTPStatus.BAD_REQUEST) @staticmethod - def create_submission(submission_dict: dict, submitter_id: int) -> SubmissionModel: + def add_statements(submission: SubmissionModel, + new_person_stmnts: list[dict], + new_ownership_stmnts: list[dict]): """ + Add the ownership and person statement records to the session. + NOTE: Each person statement needs a 1:1 link with a corresponding ownership statement. + """ + for person_stmnt in new_person_stmnts: + ownership_stmnt = SubmissionService._get_linked_ownership_stmnt(person_stmnt['statementID'], + new_ownership_stmnts) + person = Person(statement_id=uuid.uuid4(), person_json=deepcopy(person_stmnt)) + person.save_to_session() + ownership = Ownership(statement_id=uuid.uuid4(), + ownership_json=deepcopy(ownership_stmnt), + person=person, + submission=submission) + ownership.save_to_session() + try: + if person_stmnt.get('email'): + business_identifier = submission.submitted_payload['businessIdentifier'] + auth = AuthService(current_app) + entity = EntityService(current_app) + token = auth.get_bearer_token() + business = entity.get_entity_info(jwt, business_identifier).json() + delivery_address = EntityService(current_app).get_entity_info( + None, f'{business_identifier}/addresses?addressType=deliveryAddress', token).json() + business_contact = auth.get_business_contact(token, business_identifier) + business_info = {**business, **delivery_address, 'contact': {**business_contact}} + EmailService(current_app).send_added_to_btr_email(person.person_json, + business_info, + submission.submitted_payload['effectiveDate'], + token) + + except (BusinessException, ExternalServiceException) as err: + # Log error and continue to return successfully (does NOT block the submission) + current_app.logger.info(err.error) + current_app.logger.error('Error sending email for person: %s', person.id) - Create Submission - + @staticmethod + def create_submission(submission_dict: dict, submitter_id: int) -> SubmissionModel: + """ This method creates/replaces the current submission for the business using the provided submission dict. Parameters: @@ -72,30 +128,27 @@ def create_submission(submission_dict: dict, submitter_id: int) -> SubmissionMod Returns: - SubmissionModel: A SubmissionModel object that represents the created submission. - """ submission = SubmissionModel.find_by_business_identifier(submission_dict['businessIdentifier']) - if not submission: - submission = SubmissionModel() - submission.business_identifier = submission_dict['businessIdentifier'] - submission.effective_date = date.fromisoformat(submission_dict['effectiveDate']) - submission.payload = submission_dict - submission.submitter_id = submitter_id - submission.invoice_id = None - - submission.submitted_payload = submission_dict + if submission: + return SubmissionService.update_submission(submission, submission_dict, submitter_id) + + # init submission + submission = SubmissionModel(submitter_id=submitter_id, + invoice_id=None, + submitted_payload=submission_dict) + submission.save_to_session() + # add ownership / person records to session + SubmissionService.add_statements(submission, + submission.submitted_payload['personStatements'], + submission.submitted_payload['ownershipOrControlStatements']) return submission @staticmethod - def update_submission( - submission: SubmissionModel, - submission_dict: dict, - submitter_id: int, - payload: dict) -> SubmissionModel: + def update_submission(submission: SubmissionModel, + submission_dict: dict, + submitter_id: int) -> SubmissionModel: """ - - Update Submission - This method replaces the current submission for the business using the provided submission dict. Parameters: @@ -107,12 +160,44 @@ def update_submission( Returns: - SubmissionModel: A SubmissionModel object that represents the created submission. - """ - - submission.effective_date = date.fromisoformat(submission_dict['effectiveDate']) - submission.payload = submission_dict + submission.submitted_payload = submission_dict submission.submitter_id = submitter_id + # update ownership statements + new_person_stmnts = [] + new_ownership_stmnts = [] + for person_stmnt in submission_dict['personStatements']: + person_stmnt_id = person_stmnt['statementID'] + ownership_stmnt = SubmissionService._get_linked_ownership_stmnt( + person_stmnt_id, + submission_dict['ownershipOrControlStatements'] + ) + ownership_stmnt_id = ownership_stmnt['statementID'] + if current_ownership := Ownership.find_by_statement_id(ownership_stmnt_id): + if current_ownership.submission_id != submission.id: + raise BusinessException( + 'Invalid payload. Existing ownership statement linked to a different submission.', + f'Ownership statement id: {ownership_stmnt_id}', + HTTPStatus.BAD_REQUEST + ) + + if str(current_ownership.person.statement_id) != person_stmnt_id: + raise BusinessException( + 'Invalid payload. Existing ownership statement linked to a different person.', + f'Ownership statement id: {ownership_stmnt_id}', + HTTPStatus.BAD_REQUEST + ) + # TODO: 'deep_spread' needs to be fixed #23489 + current_ownership.ownership_json = deep_spread(current_ownership.ownership_json, + ownership_stmnt) + + current_ownership.person.person_json = deep_spread(current_ownership.person.person_json, + person_stmnt) + current_ownership.save_to_session() + else: + new_ownership_stmnts.append(ownership_stmnt) + new_person_stmnts.append(person_stmnt) + + SubmissionService.add_statements(submission, new_person_stmnts, new_ownership_stmnts) - submission.submitted_payload = payload return submission diff --git a/btr-api/tests/postman/btr-api.postman_collection.json b/btr-api/tests/postman/btr-api.postman_collection.json index 1b5e8943..ca3ce050 100644 --- a/btr-api/tests/postman/btr-api.postman_collection.json +++ b/btr-api/tests/postman/btr-api.postman_collection.json @@ -120,7 +120,7 @@ "name": "submission", "item": [ { - "name": "create", + "name": "get latest for entity", "request": { "auth": { "type": "bearer", @@ -132,36 +132,48 @@ } ] }, - "method": "POST", + "method": "GET", "header": [ { "key": "Account-Id", - "value": "{{staff_account_id}}" + "value": "{{staff_account_id}}", + "type": "text" } ], - "body": { - "mode": "raw", - "raw": "{\r\n \"noSignificantIndividualsExist\": false,\r\n \"businessIdentifier\": \"BC0871427\",\r\n \"effectiveDate\": \"2024-05-23\",\r\n \"entityStatement\": {\r\n \"entityType\": \"legalEntity\",\r\n \"identifiers\": [],\r\n \"isComponent\": false,\r\n \"publicationDetails\": {\r\n \"bodsVersion\": \"0.3\",\r\n \"publicationDate\": \"2024-05-23\",\r\n \"publisher\": {\r\n \"name\": \"BCROS - BC Registries and Online Services\",\r\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\r\n }\r\n },\r\n \"source\": {\r\n \"type\": [\r\n \"officialRegister\",\r\n \"verified\"\r\n ],\r\n \"assertedBy\": [\r\n {\r\n \"name\": \"BCROS - BC Registries and Online Services\",\r\n \"uri\": \"https://www.bcregistry.gov.bc.ca/\"\r\n }\r\n ],\r\n \"retrievedAt\": \"2024-05-23T18:04:37.270Z\"\r\n },\r\n \"statementDate\": \"2024-05-23\",\r\n \"statementID\": \"24ac9ef0-de77-4435-9ee5-500244444116\",\r\n \"statementType\": \"entityStatement\",\r\n \"name\": \"0871427 B.C. LTD.\"\r\n },\r\n \"ownershipOrControlStatements\": [\r\n {\r\n \"statementID\": \"3bffdb3b-fc1c-441c-bed9-ca561dd64412\",\r\n \"interestedParty\": {\r\n \"describedByPersonStatement\": \"c7c1778b-bfd9-4f50-8e31-dd3aae5bea6a\"\r\n },\r\n \"interests\": [\r\n {\r\n \"directOrIndirect\": \"direct\",\r\n \"details\": \"controlType.shares.beneficialOwner\",\r\n \"type\": \"shareholding\",\r\n \"startDate\": \"2024-05-23\",\r\n \"share\": {\r\n \"exclusiveMinimum\": false,\r\n \"exclusiveMaximum\": true,\r\n \"minimum\": 0,\r\n \"maximum\": 25\r\n }\r\n }\r\n ],\r\n \"isComponent\": false,\r\n \"publicationDetails\": {\r\n \"bodsVersion\": \"0.3\",\r\n \"publicationDate\": \"2024-05-23\",\r\n \"publisher\": {\r\n \"name\": \"BCROS - BC Registries and Online Services\",\r\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\r\n }\r\n },\r\n \"source\": {\r\n \"type\": [\r\n \"selfDeclaration\"\r\n ],\r\n \"assertedBy\": [\r\n {\r\n \"name\": \"testera fdfdsf\"\r\n }\r\n ],\r\n \"description\": \"Using Gov BC - BTR - Web UI\"\r\n },\r\n \"statementDate\": \"2024-05-23\",\r\n \"statementType\": \"ownershipOrControlStatement\",\r\n \"subject\": {\r\n \"describedByEntityStatement\": \"\"\r\n }\r\n }\r\n ],\r\n \"personStatements\": [\r\n {\r\n \"missingInfoReason\": \"\",\r\n \"placeOfResidence\": {\r\n \"street\": \"D-15 Dodds Rd\",\r\n \"streetAdditional\": \"\",\r\n \"city\": \"Headingley\",\r\n \"region\": \"MB\",\r\n \"postalCode\": \"R4H 1E3\",\r\n \"locationDescription\": \"\",\r\n \"country\": \"CA\",\r\n \"countryName\": \"Canada\"\r\n },\r\n \"addresses\": [\r\n {\r\n \"street\": \"D-15 Dodds Rd\",\r\n \"streetAdditional\": \"\",\r\n \"city\": \"Headingley\",\r\n \"region\": \"MB\",\r\n \"postalCode\": \"R4H 1E3\",\r\n \"locationDescription\": \"\",\r\n \"country\": \"CA\",\r\n \"countryName\": \"Canada\"\r\n }\r\n ],\r\n \"birthDate\": \"2024-04-04\",\r\n \"email\": \"test.test123@test.com\",\r\n \"hasTaxNumber\": false,\r\n \"identifiers\": [],\r\n \"isComponent\": false,\r\n \"names\": [\r\n {\r\n \"fullName\": \"testera fdfdsf\",\r\n \"type\": \"individual\"\r\n }\r\n ],\r\n \"nationalities\": [\r\n {\r\n \"name\": \"Canada\",\r\n \"code\": \"CA\"\r\n }\r\n ],\r\n \"isPermanentResidentCa\": false,\r\n \"personType\": \"knownPerson\",\r\n \"publicationDetails\": {\r\n \"bodsVersion\": \"0.3\",\r\n \"publicationDate\": \"2024-05-23\",\r\n \"publisher\": {\r\n \"name\": \"BCROS - BC Registries and Online Services\",\r\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\r\n }\r\n },\r\n \"source\": {\r\n \"type\": [\r\n \"selfDeclaration\"\r\n ],\r\n \"assertedBy\": [\r\n {\r\n \"name\": \"testera fdfdsf\"\r\n }\r\n ],\r\n \"description\": \"Using Gov BC - BTR - Web UI\"\r\n },\r\n \"statementDate\": \"2024-05-23\",\r\n \"statementType\": \"personStatement\",\r\n \"taxResidencies\": [],\r\n \"determinationOfIncapacity\": true,\r\n \"statementID\": \"c7c1778b-bfd9-4f50-8e31-dd3aae5bea6a\",\r\n \"uuid\": \"84d36ff4-c002-42fa-bfcd-e3cca9d01ef2\"\r\n }\r\n ]\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, "url": { - "raw": "{{internal_url}}/plots", + "raw": "{{internal_url}}/plots/entity/:identifier", "host": [ "{{internal_url}}" ], "path": [ - "plots" + "plots", + "entity", + ":identifier" + ], + "variable": [ + { + "key": "identifier", + "value": "BC1230113" + } ] } }, "response": [] }, { - "name": "receipt", + "name": "create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], "request": { "auth": { "type": "bearer", @@ -177,12 +189,12 @@ "header": [ { "key": "Account-Id", - "value": "{{staff_account_id}}" + "value": "{{account_id}}" } ], "body": { "mode": "raw", - "raw": "{\n \"corpName\": \"test corpName\",\n \"filingDateTime\": \"2024-01-25\",\n \"filingIdentifier\": \"1\",\n \"businessNumber\": \"123\"\n}", + "raw": "{\r\n \"businessIdentifier\": \"BC1230113\",\r\n \"effectiveDate\": \"2024-09-19\",\r\n \"entityStatement\": {\r\n \"entityType\": \"legalEntity\",\r\n \"identifiers\": [],\r\n \"isComponent\": false,\r\n \"name\": \"1230113 B.C. LTD.\",\r\n \"publicationDetails\": {\r\n \"bodsVersion\": \"0.3\",\r\n \"publicationDate\": \"2024-09-19\",\r\n \"publisher\": {\r\n \"name\": \"BCROS - BC Registries and Online Services\",\r\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\r\n }\r\n },\r\n \"source\": {\r\n \"assertedBy\": [\r\n {\r\n \"name\": \"BCROS - BC Registries and Online Services\",\r\n \"uri\": \"https://www.bcregistry.gov.bc.ca/\"\r\n }\r\n ],\r\n \"retrievedAt\": \"2024-09-19T13:59:55.310Z\",\r\n \"type\": [\r\n \"officialRegister\",\r\n \"verified\"\r\n ]\r\n },\r\n \"statementDate\": \"2024-09-19\",\r\n \"statementID\": \"0df55746-4d87-4500-ba14-f16e4a91bda6\",\r\n \"statementType\": \"entityStatement\"\r\n },\r\n \"noSignificantIndividualsExist\": false,\r\n \"ownershipOrControlStatements\": [\r\n {\r\n \"interestedParty\": {\r\n \"describedByPersonStatement\": \"1199dc30-6cd8-47fa-be79-f057348ab36b\"\r\n },\r\n \"interests\": [\r\n {\r\n \"details\": \"controlType.shares.beneficialOwner\",\r\n \"directOrIndirect\": \"direct\",\r\n \"share\": {\r\n \"exclusiveMaximum\": false,\r\n \"exclusiveMinimum\": false,\r\n \"maximum\": 50,\r\n \"minimum\": 25\r\n },\r\n \"startDate\": \"2014-11-07\",\r\n \"type\": \"shareholding\"\r\n },\r\n {\r\n \"details\": \"controlType.shares.registeredOwner\",\r\n \"directOrIndirect\": \"direct\",\r\n \"share\": {\r\n \"exclusiveMaximum\": false,\r\n \"exclusiveMinimum\": false,\r\n \"maximum\": 50,\r\n \"minimum\": 25\r\n },\r\n \"startDate\": \"2014-11-07\",\r\n \"type\": \"shareholding\"\r\n },\r\n {\r\n \"details\": \"controlType.shares.actingJointly\",\r\n \"connectedIndividuals\": [{\"uuid\": \"ce935e86-f4b9-4938-b12e-29c5e5cc213d\"}],\r\n \"directOrIndirect\": \"direct\",\r\n \"share\": {\r\n \"exclusiveMaximum\": false,\r\n \"exclusiveMinimum\": false,\r\n \"maximum\": 50,\r\n \"minimum\": 25\r\n },\r\n \"startDate\": \"2014-11-07\",\r\n \"type\": \"shareholding\"\r\n },\r\n {\r\n \"details\": \"controlType.votes.beneficialOwner\",\r\n \"directOrIndirect\": \"direct\",\r\n \"share\": {\r\n \"exclusiveMaximum\": false,\r\n \"exclusiveMinimum\": true,\r\n \"maximum\": 75,\r\n \"minimum\": 50\r\n },\r\n \"startDate\": \"2014-11-07\",\r\n \"type\": \"votingRights\"\r\n }\r\n ],\r\n \"isComponent\": false,\r\n \"publicationDetails\": {\r\n \"bodsVersion\": \"0.3\",\r\n \"publicationDate\": \"2024-09-11\",\r\n \"publisher\": {\r\n \"name\": \"BCROS - BC Registries and Online Services\",\r\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\r\n }\r\n },\r\n \"source\": {\r\n \"assertedBy\": [\r\n {\r\n \"name\": \"Kial Jinnah\"\r\n }\r\n ],\r\n \"description\": \"Using Gov BC - BTR - Web UI\",\r\n \"type\": [\r\n \"selfDeclaration\"\r\n ]\r\n },\r\n \"statementDate\": \"2024-09-11\",\r\n \"statementID\": \"6c08495f-c9d7-4c85-8d4f-8ed7c108fe3d\",\r\n \"statementType\": \"ownershipOrControlStatement\",\r\n \"subject\": {\r\n \"describedByEntityStatement\": \"\"\r\n }\r\n },\r\n {\r\n \"interestedParty\": {\r\n \"describedByPersonStatement\": \"ce935e86-f4b9-4938-b12e-29c5e5cc213d\"\r\n },\r\n \"interests\": [\r\n {\r\n \"details\": \"controlType.shares.indirectControl\",\r\n \"directOrIndirect\": \"indirect\",\r\n \"share\": {\r\n \"exclusiveMaximum\": true,\r\n \"exclusiveMinimum\": false,\r\n \"maximum\": 25,\r\n \"minimum\": 0\r\n },\r\n \"startDate\": \"2021-09-07\",\r\n \"type\": \"shareholding\"\r\n },\r\n {\r\n \"details\": \"controlType.shares.actingJointly\",\r\n \"connectedIndividuals\": [{\"uuid\": \"a4b3844a-f68b-4092-b490-85a603f6d424\"}],\r\n \"directOrIndirect\": \"direct\",\r\n \"share\": {\r\n \"exclusiveMaximum\": false,\r\n \"exclusiveMinimum\": false,\r\n \"maximum\": 50,\r\n \"minimum\": 25\r\n },\r\n \"startDate\": \"2014-11-07\",\r\n \"type\": \"shareholding\"\r\n }\r\n ],\r\n \"isComponent\": false,\r\n \"publicationDetails\": {\r\n \"bodsVersion\": \"0.3\",\r\n \"publicationDate\": \"2024-09-12\",\r\n \"publisher\": {\r\n \"name\": \"BCROS - BC Registries and Online Services\",\r\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\r\n }\r\n },\r\n \"source\": {\r\n \"assertedBy\": [\r\n {\r\n \"name\": \"Waffles Butter\"\r\n }\r\n ],\r\n \"description\": \"Using Gov BC - BTR - Web UI\",\r\n \"type\": [\r\n \"selfDeclaration\"\r\n ]\r\n },\r\n \"statementDate\": \"2024-09-12\",\r\n \"statementID\": \"f4d9f29b-559b-4353-9bd4-89ada5ae5209\",\r\n \"statementType\": \"ownershipOrControlStatement\",\r\n \"subject\": {\r\n \"describedByEntityStatement\": \"\"\r\n }\r\n },\r\n {\r\n \"interestedParty\": {\r\n \"describedByPersonStatement\": \"4b7863a1-4fbf-42a5-afe8-8f4a4f3ca049\"\r\n },\r\n \"interests\": [\r\n {\r\n \"details\": \"controlType.shares.indirectControl\",\r\n \"directOrIndirect\": \"indirect\",\r\n \"share\": {\r\n \"exclusiveMaximum\": false,\r\n \"exclusiveMinimum\": false,\r\n \"maximum\": 50,\r\n \"minimum\": 25\r\n },\r\n \"startDate\": \"2019-09-19\",\r\n \"type\": \"shareholding\"\r\n },\r\n {\r\n \"details\": \"controlType.votes.registeredOwner\",\r\n \"directOrIndirect\": \"direct\",\r\n \"share\": {\r\n \"exclusiveMaximum\": false,\r\n \"exclusiveMinimum\": false,\r\n \"maximum\": 50,\r\n \"minimum\": 25\r\n },\r\n \"startDate\": \"2019-09-19\",\r\n \"type\": \"votingRights\"\r\n }\r\n ],\r\n \"isComponent\": false,\r\n \"publicationDetails\": {\r\n \"bodsVersion\": \"0.3\",\r\n \"publicationDate\": \"2024-09-16\",\r\n \"publisher\": {\r\n \"name\": \"BCROS - BC Registries and Online Services\",\r\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\r\n }\r\n },\r\n \"source\": {\r\n \"assertedBy\": [\r\n {\r\n \"name\": \"Wallaby Willow\"\r\n }\r\n ],\r\n \"description\": \"Using Gov BC - BTR - Web UI\",\r\n \"type\": [\r\n \"selfDeclaration\"\r\n ]\r\n },\r\n \"statementDate\": \"2024-09-16\",\r\n \"statementID\": \"aef71bd1-8c64-4fff-a2d5-9a25b450745d\",\r\n \"statementType\": \"ownershipOrControlStatement\",\r\n \"subject\": {\r\n \"describedByEntityStatement\": \"\"\r\n }\r\n },\r\n {\r\n \"interestedParty\": {\r\n \"describedByPersonStatement\": \"156cbfa3-ec28-42cf-8077-0b512c0b7dac\"\r\n },\r\n \"interests\": [\r\n {\r\n \"details\": \"controlType.shares.registeredOwner\",\r\n \"directOrIndirect\": \"direct\",\r\n \"share\": {\r\n \"exclusiveMaximum\": false,\r\n \"exclusiveMinimum\": false,\r\n \"maximum\": 50,\r\n \"minimum\": 25\r\n },\r\n \"startDate\": \"2024-09-02\",\r\n \"type\": \"shareholding\"\r\n }\r\n ],\r\n \"isComponent\": false,\r\n \"publicationDetails\": {\r\n \"bodsVersion\": \"0.3\",\r\n \"publicationDate\": \"2024-09-19\",\r\n \"publisher\": {\r\n \"name\": \"BCROS - BC Registries and Online Services\",\r\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\r\n }\r\n },\r\n \"source\": {\r\n \"assertedBy\": [\r\n {\r\n \"name\": \"testing tester\"\r\n }\r\n ],\r\n \"description\": \"Using Gov BC - BTR - Web UI\",\r\n \"type\": [\r\n \"selfDeclaration\"\r\n ]\r\n },\r\n \"statementDate\": \"2024-09-19\",\r\n \"statementID\": \"46b57ace-1c66-4181-ace8-5d05d41b2c19\",\r\n \"statementType\": \"ownershipOrControlStatement\",\r\n \"subject\": {\r\n \"describedByEntityStatement\": \"\"\r\n }\r\n }\r\n ],\r\n \"personStatements\": [\r\n {\r\n \"addresses\": [\r\n {\r\n \"city\": \"Edmonton\",\r\n \"country\": \"CA\",\r\n \"countryName\": \"Canada\",\r\n \"postalCode\": \"T6T 1B6\",\r\n \"region\": \"AB\",\r\n \"street\": \"4323 33 St NW\",\r\n \"streetAdditional\": \"\"\r\n }\r\n ],\r\n \"birthDate\": \"2014-11-07\",\r\n \"email\": \"kial@daxiom.com\",\r\n \"hasTaxNumber\": true,\r\n \"identifiers\": [\r\n {\r\n \"id\": \"711 612 325\",\r\n \"scheme\": \"CAN-TAXID\",\r\n \"schemeName\": \"ITN\"\r\n }\r\n ],\r\n \"isComponent\": false,\r\n \"isPermanentResidentCa\": false,\r\n \"names\": [\r\n {\r\n \"fullName\": \"Kial Jinnah\",\r\n \"type\": \"individual\"\r\n }\r\n ],\r\n \"nationalities\": [\r\n {\r\n \"code\": \"CA\",\r\n \"name\": \"Canada\"\r\n }\r\n ],\r\n \"personType\": \"knownPerson\",\r\n \"phoneNumber\": {\r\n \"countryCallingCode\": \"1\",\r\n \"countryCode2letterIso\": \"CA\",\r\n \"number\": \"7783888844\"\r\n },\r\n \"placeOfResidence\": {\r\n \"city\": \"Edmonton\",\r\n \"country\": \"CA\",\r\n \"countryName\": \"Canada\",\r\n \"postalCode\": \"T6T 1B6\",\r\n \"region\": \"AB\",\r\n \"street\": \"4323 33 St NW\",\r\n \"streetAdditional\": \"\"\r\n },\r\n \"publicationDetails\": {\r\n \"bodsVersion\": \"0.3\",\r\n \"publicationDate\": \"2024-09-11\",\r\n \"publisher\": {\r\n \"name\": \"BCROS - BC Registries and Online Services\",\r\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\r\n }\r\n },\r\n \"source\": {\r\n \"assertedBy\": [\r\n {\r\n \"name\": \"Kial Jinnah\"\r\n }\r\n ],\r\n \"description\": \"Using Gov BC - BTR - Web UI\",\r\n \"type\": [\r\n \"selfDeclaration\"\r\n ]\r\n },\r\n \"statementDate\": \"2024-09-11\",\r\n \"statementID\": \"1199dc30-6cd8-47fa-be79-f057348ab36b\",\r\n \"statementType\": \"personStatement\",\r\n \"taxResidencies\": [\r\n {\r\n \"code\": \"CA\",\r\n \"name\": \"Canada\"\r\n }\r\n ],\r\n \"uuid\": \"a4b3844a-f68b-4092-b490-85a603f6d424\"\r\n },\r\n {\r\n \"addresses\": [\r\n {\r\n \"city\": \"Vancouver\",\r\n \"country\": \"CA\",\r\n \"countryName\": \"Canada\",\r\n \"postalCode\": \"V6H 2T8\",\r\n \"region\": \"BC\",\r\n \"street\": \"Th-3023 Birch St\",\r\n \"streetAdditional\": \"\"\r\n }\r\n ],\r\n \"birthDate\": \"2000-02-02\",\r\n \"email\": \"kial@daxiom.com\",\r\n \"hasTaxNumber\": true,\r\n \"identifiers\": [\r\n {\r\n \"id\": \"402 931 299\",\r\n \"scheme\": \"CAN-TAXID\",\r\n \"schemeName\": \"ITN\"\r\n }\r\n ],\r\n \"isComponent\": false,\r\n \"isPermanentResidentCa\": true,\r\n \"names\": [\r\n {\r\n \"fullName\": \"Wallaby Willow\",\r\n \"type\": \"individual\"\r\n }\r\n ],\r\n \"nationalities\": [\r\n {\r\n \"code\": \"AL\",\r\n \"name\": \"Albania\"\r\n },\r\n {\r\n \"code\": \"BZ\",\r\n \"name\": \"Belize\"\r\n }\r\n ],\r\n \"personType\": \"knownPerson\",\r\n \"phoneNumber\": {\r\n \"countryCallingCode\": \"1\",\r\n \"countryCode2letterIso\": \"CA\",\r\n \"number\": \"2508747772\"\r\n },\r\n \"placeOfResidence\": {\r\n \"city\": \"Vancouver\",\r\n \"country\": \"CA\",\r\n \"countryName\": \"Canada\",\r\n \"postalCode\": \"V6H 2T8\",\r\n \"region\": \"BC\",\r\n \"street\": \"Th-3023 Birch St\",\r\n \"streetAdditional\": \"\"\r\n },\r\n \"publicationDetails\": {\r\n \"bodsVersion\": \"0.3\",\r\n \"publicationDate\": \"2024-09-16\",\r\n \"publisher\": {\r\n \"name\": \"BCROS - BC Registries and Online Services\",\r\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\r\n }\r\n },\r\n \"source\": {\r\n \"assertedBy\": [\r\n {\r\n \"name\": \"Wallaby Willow\"\r\n }\r\n ],\r\n \"description\": \"Using Gov BC - BTR - Web UI\",\r\n \"type\": [\r\n \"selfDeclaration\"\r\n ]\r\n },\r\n \"statementDate\": \"2024-09-16\",\r\n \"statementID\": \"4b7863a1-4fbf-42a5-afe8-8f4a4f3ca049\",\r\n \"statementType\": \"personStatement\",\r\n \"taxResidencies\": [\r\n {\r\n \"code\": \"CA\",\r\n \"name\": \"Canada\"\r\n }\r\n ],\r\n \"uuid\": \"1a825cce-a3fa-47b2-b8c3-e2fae40ac7df\"\r\n },\r\n {\r\n \"addresses\": [\r\n {\r\n \"city\": \"Longueuil\",\r\n \"country\": \"CA\",\r\n \"countryName\": \"Canada\",\r\n \"postalCode\": \"J4H 3X9\",\r\n \"region\": \"QC\",\r\n \"street\": \"433-405 Ch De Chambly\",\r\n \"streetAdditional\": \"\"\r\n }\r\n ],\r\n \"birthDate\": \"2005-09-13\",\r\n \"email\": \"kial@daxiom.com\",\r\n \"hasTaxNumber\": false,\r\n \"identifiers\": [],\r\n \"isComponent\": false,\r\n \"isPermanentResidentCa\": false,\r\n \"names\": [\r\n {\r\n \"fullName\": \"Waffles Butter\",\r\n \"type\": \"individual\"\r\n }\r\n ],\r\n \"nationalities\": [\r\n {\r\n \"code\": \"US\",\r\n \"name\": \"United States\"\r\n }\r\n ],\r\n \"personType\": \"knownPerson\",\r\n \"phoneNumber\": {\r\n \"countryCallingCode\": \"1\",\r\n \"countryCode2letterIso\": \"CA\",\r\n \"number\": \"7784467467\"\r\n },\r\n \"placeOfResidence\": {\r\n \"city\": \"Longueuil\",\r\n \"country\": \"CA\",\r\n \"countryName\": \"Canada\",\r\n \"postalCode\": \"J4H 3X9\",\r\n \"region\": \"QC\",\r\n \"street\": \"433-405 Ch De Chambly\",\r\n \"streetAdditional\": \"\"\r\n },\r\n \"publicationDetails\": {\r\n \"bodsVersion\": \"0.3\",\r\n \"publicationDate\": \"2024-09-12\",\r\n \"publisher\": {\r\n \"name\": \"BCROS - BC Registries and Online Services\",\r\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\r\n }\r\n },\r\n \"source\": {\r\n \"assertedBy\": [\r\n {\r\n \"name\": \"Waffles Butter\"\r\n }\r\n ],\r\n \"description\": \"Using Gov BC - BTR - Web UI\",\r\n \"type\": [\r\n \"selfDeclaration\"\r\n ]\r\n },\r\n \"statementDate\": \"2024-09-12\",\r\n \"statementID\": \"ce935e86-f4b9-4938-b12e-29c5e5cc213d\",\r\n \"statementType\": \"personStatement\",\r\n \"taxResidencies\": [],\r\n \"uuid\": \"ce935e86-f4b9-4938-b12e-29c5e5cc213d\"\r\n },\r\n {\r\n \"addresses\": [\r\n {\r\n \"city\": \"Kitchener\",\r\n \"country\": \"CA\",\r\n \"countryName\": \"Canada\",\r\n \"postalCode\": \"N2E 3H6\",\r\n \"region\": \"ON\",\r\n \"street\": \"44-35 Howe Dr\",\r\n \"streetAdditional\": \"\"\r\n }\r\n ],\r\n \"birthDate\": \"2003-09-03\",\r\n \"email\": \"kial@daxiom.com\",\r\n \"hasTaxNumber\": false,\r\n \"identifiers\": [],\r\n \"isComponent\": false,\r\n \"isPermanentResidentCa\": false,\r\n \"names\": [\r\n {\r\n \"fullName\": \"testing tester\",\r\n \"type\": \"individual\"\r\n }\r\n ],\r\n \"nationalities\": [\r\n {\r\n \"code\": \"CA\",\r\n \"name\": \"Canada\"\r\n }\r\n ],\r\n \"personType\": \"knownPerson\",\r\n \"phoneNumber\": {\r\n \"countryCallingCode\": \"1\",\r\n \"countryCode2letterIso\": \"CA\",\r\n \"number\": \"6139585888\"\r\n },\r\n \"placeOfResidence\": {\r\n \"city\": \"Kitchener\",\r\n \"country\": \"CA\",\r\n \"countryName\": \"Canada\",\r\n \"postalCode\": \"N2E 3H6\",\r\n \"region\": \"ON\",\r\n \"street\": \"44-35 Howe Dr\",\r\n \"streetAdditional\": \"\"\r\n },\r\n \"publicationDetails\": {\r\n \"bodsVersion\": \"0.3\",\r\n \"publicationDate\": \"2024-09-19\",\r\n \"publisher\": {\r\n \"name\": \"BCROS - BC Registries and Online Services\",\r\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\r\n }\r\n },\r\n \"source\": {\r\n \"assertedBy\": [\r\n {\r\n \"name\": \"testing tester\"\r\n }\r\n ],\r\n \"description\": \"Using Gov BC - BTR - Web UI\",\r\n \"type\": [\r\n \"selfDeclaration\"\r\n ]\r\n },\r\n \"statementDate\": \"2024-09-19\",\r\n \"statementID\": \"156cbfa3-ec28-42cf-8077-0b512c0b7dac\",\r\n \"statementType\": \"personStatement\",\r\n \"taxResidencies\": [],\r\n \"uuid\": \"6cd3eba0-ab93-42b0-98be-22e8b1397fea\"\r\n }\r\n ]\r\n}", "options": { "raw": { "language": "json" @@ -190,55 +202,85 @@ } }, "url": { - "raw": "{{pay_api_url}}/payment-requests/:invoice_id/receipts", + "raw": "{{internal_url}}/plots", "host": [ - "{{pay_api_url}}" + "{{internal_url}}" ], "path": [ - "payment-requests", - ":invoice_id", - "receipts" - ], - "variable": [ - { - "key": "invoice_id", - "value": "33600", - "description": "invoice id in pay db" - } + "plots" ] } }, - "response": [] - }, - { - "name": "fetch", - "request": { - "auth": { - "type": "bearer", - "bearer": [ + "response": [ + { + "name": "create", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "Account-Id", + "value": "{{account_id}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"businessIdentifier\": \"BC1230113\",\r\n \"effectiveDate\": \"2024-09-19\",\r\n \"entityStatement\": {\r\n \"entityType\": \"legalEntity\",\r\n \"identifiers\": [],\r\n \"isComponent\": false,\r\n \"name\": \"1230113 B.C. LTD.\",\r\n \"publicationDetails\": {\r\n \"bodsVersion\": \"0.3\",\r\n \"publicationDate\": \"2024-09-19\",\r\n \"publisher\": {\r\n \"name\": \"BCROS - BC Registries and Online Services\",\r\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\r\n }\r\n },\r\n \"source\": {\r\n \"assertedBy\": [\r\n {\r\n \"name\": \"BCROS - BC Registries and Online Services\",\r\n \"uri\": \"https://www.bcregistry.gov.bc.ca/\"\r\n }\r\n ],\r\n \"retrievedAt\": \"2024-09-19T13:59:55.310Z\",\r\n \"type\": [\r\n \"officialRegister\",\r\n \"verified\"\r\n ]\r\n },\r\n \"statementDate\": \"2024-09-19\",\r\n \"statementID\": \"0df55746-4d87-4500-ba14-f16e4a91bda6\",\r\n \"statementType\": \"entityStatement\"\r\n },\r\n \"noSignificantIndividualsExist\": false,\r\n \"ownershipOrControlStatements\": [\r\n {\r\n \"interestedParty\": {\r\n \"describedByPersonStatement\": \"1199dc30-6cd8-47fa-be79-f057348ab36b\"\r\n },\r\n \"interests\": [\r\n {\r\n \"details\": \"controlType.shares.beneficialOwner\",\r\n \"directOrIndirect\": \"direct\",\r\n \"share\": {\r\n \"exclusiveMaximum\": false,\r\n \"exclusiveMinimum\": false,\r\n \"maximum\": 50,\r\n \"minimum\": 25\r\n },\r\n \"startDate\": \"2014-11-07\",\r\n \"type\": \"shareholding\"\r\n },\r\n {\r\n \"details\": \"controlType.shares.registeredOwner\",\r\n \"directOrIndirect\": \"direct\",\r\n \"share\": {\r\n \"exclusiveMaximum\": false,\r\n \"exclusiveMinimum\": false,\r\n \"maximum\": 50,\r\n \"minimum\": 25\r\n },\r\n \"startDate\": \"2014-11-07\",\r\n \"type\": \"shareholding\"\r\n },\r\n {\r\n \"details\": \"controlType.shares.actingJointly\",\r\n \"connectedIndividuals\": [{\"uuid\": \"ce935e86-f4b9-4938-b12e-29c5e5cc213d\"}],\r\n \"directOrIndirect\": \"direct\",\r\n \"share\": {\r\n \"exclusiveMaximum\": false,\r\n \"exclusiveMinimum\": false,\r\n \"maximum\": 50,\r\n \"minimum\": 25\r\n },\r\n \"startDate\": \"2014-11-07\",\r\n \"type\": \"shareholding\"\r\n },\r\n {\r\n \"details\": \"controlType.votes.beneficialOwner\",\r\n \"directOrIndirect\": \"direct\",\r\n \"share\": {\r\n \"exclusiveMaximum\": false,\r\n \"exclusiveMinimum\": true,\r\n \"maximum\": 75,\r\n \"minimum\": 50\r\n },\r\n \"startDate\": \"2014-11-07\",\r\n \"type\": \"votingRights\"\r\n }\r\n ],\r\n \"isComponent\": false,\r\n \"publicationDetails\": {\r\n \"bodsVersion\": \"0.3\",\r\n \"publicationDate\": \"2024-09-11\",\r\n \"publisher\": {\r\n \"name\": \"BCROS - BC Registries and Online Services\",\r\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\r\n }\r\n },\r\n \"source\": {\r\n \"assertedBy\": [\r\n {\r\n \"name\": \"Kial Jinnah\"\r\n }\r\n ],\r\n \"description\": \"Using Gov BC - BTR - Web UI\",\r\n \"type\": [\r\n \"selfDeclaration\"\r\n ]\r\n },\r\n \"statementDate\": \"2024-09-11\",\r\n \"statementID\": \"6c08495f-c9d7-4c85-8d4f-8ed7c108fe3d\",\r\n \"statementType\": \"ownershipOrControlStatement\",\r\n \"subject\": {\r\n \"describedByEntityStatement\": \"\"\r\n }\r\n },\r\n {\r\n \"interestedParty\": {\r\n \"describedByPersonStatement\": \"ce935e86-f4b9-4938-b12e-29c5e5cc213d\"\r\n },\r\n \"interests\": [\r\n {\r\n \"details\": \"controlType.shares.indirectControl\",\r\n \"directOrIndirect\": \"indirect\",\r\n \"share\": {\r\n \"exclusiveMaximum\": true,\r\n \"exclusiveMinimum\": false,\r\n \"maximum\": 25,\r\n \"minimum\": 0\r\n },\r\n \"startDate\": \"2021-09-07\",\r\n \"type\": \"shareholding\"\r\n },\r\n {\r\n \"details\": \"controlType.shares.actingJointly\",\r\n \"connectedIndividuals\": [{\"uuid\": \"a4b3844a-f68b-4092-b490-85a603f6d424\"}],\r\n \"directOrIndirect\": \"direct\",\r\n \"share\": {\r\n \"exclusiveMaximum\": false,\r\n \"exclusiveMinimum\": false,\r\n \"maximum\": 50,\r\n \"minimum\": 25\r\n },\r\n \"startDate\": \"2014-11-07\",\r\n \"type\": \"shareholding\"\r\n }\r\n ],\r\n \"isComponent\": false,\r\n \"publicationDetails\": {\r\n \"bodsVersion\": \"0.3\",\r\n \"publicationDate\": \"2024-09-12\",\r\n \"publisher\": {\r\n \"name\": \"BCROS - BC Registries and Online Services\",\r\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\r\n }\r\n },\r\n \"source\": {\r\n \"assertedBy\": [\r\n {\r\n \"name\": \"Waffles Butter\"\r\n }\r\n ],\r\n \"description\": \"Using Gov BC - BTR - Web UI\",\r\n \"type\": [\r\n \"selfDeclaration\"\r\n ]\r\n },\r\n \"statementDate\": \"2024-09-12\",\r\n \"statementID\": \"f4d9f29b-559b-4353-9bd4-89ada5ae5209\",\r\n \"statementType\": \"ownershipOrControlStatement\",\r\n \"subject\": {\r\n \"describedByEntityStatement\": \"\"\r\n }\r\n },\r\n {\r\n \"interestedParty\": {\r\n \"describedByPersonStatement\": \"4b7863a1-4fbf-42a5-afe8-8f4a4f3ca049\"\r\n },\r\n \"interests\": [\r\n {\r\n \"details\": \"controlType.shares.indirectControl\",\r\n \"directOrIndirect\": \"indirect\",\r\n \"share\": {\r\n \"exclusiveMaximum\": false,\r\n \"exclusiveMinimum\": false,\r\n \"maximum\": 50,\r\n \"minimum\": 25\r\n },\r\n \"startDate\": \"2019-09-19\",\r\n \"type\": \"shareholding\"\r\n },\r\n {\r\n \"details\": \"controlType.votes.registeredOwner\",\r\n \"directOrIndirect\": \"direct\",\r\n \"share\": {\r\n \"exclusiveMaximum\": false,\r\n \"exclusiveMinimum\": false,\r\n \"maximum\": 50,\r\n \"minimum\": 25\r\n },\r\n \"startDate\": \"2019-09-19\",\r\n \"type\": \"votingRights\"\r\n }\r\n ],\r\n \"isComponent\": false,\r\n \"publicationDetails\": {\r\n \"bodsVersion\": \"0.3\",\r\n \"publicationDate\": \"2024-09-16\",\r\n \"publisher\": {\r\n \"name\": \"BCROS - BC Registries and Online Services\",\r\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\r\n }\r\n },\r\n \"source\": {\r\n \"assertedBy\": [\r\n {\r\n \"name\": \"Wallaby Willow\"\r\n }\r\n ],\r\n \"description\": \"Using Gov BC - BTR - Web UI\",\r\n \"type\": [\r\n \"selfDeclaration\"\r\n ]\r\n },\r\n \"statementDate\": \"2024-09-16\",\r\n \"statementID\": \"aef71bd1-8c64-4fff-a2d5-9a25b450745d\",\r\n \"statementType\": \"ownershipOrControlStatement\",\r\n \"subject\": {\r\n \"describedByEntityStatement\": \"\"\r\n }\r\n },\r\n {\r\n \"interestedParty\": {\r\n \"describedByPersonStatement\": \"156cbfa3-ec28-42cf-8077-0b512c0b7dac\"\r\n },\r\n \"interests\": [\r\n {\r\n \"details\": \"controlType.shares.registeredOwner\",\r\n \"directOrIndirect\": \"direct\",\r\n \"share\": {\r\n \"exclusiveMaximum\": false,\r\n \"exclusiveMinimum\": false,\r\n \"maximum\": 50,\r\n \"minimum\": 25\r\n },\r\n \"startDate\": \"2024-09-02\",\r\n \"type\": \"shareholding\"\r\n }\r\n ],\r\n \"isComponent\": false,\r\n \"publicationDetails\": {\r\n \"bodsVersion\": \"0.3\",\r\n \"publicationDate\": \"2024-09-19\",\r\n \"publisher\": {\r\n \"name\": \"BCROS - BC Registries and Online Services\",\r\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\r\n }\r\n },\r\n \"source\": {\r\n \"assertedBy\": [\r\n {\r\n \"name\": \"testing tester\"\r\n }\r\n ],\r\n \"description\": \"Using Gov BC - BTR - Web UI\",\r\n \"type\": [\r\n \"selfDeclaration\"\r\n ]\r\n },\r\n \"statementDate\": \"2024-09-19\",\r\n \"statementID\": \"46b57ace-1c66-4181-ace8-5d05d41b2c19\",\r\n \"statementType\": \"ownershipOrControlStatement\",\r\n \"subject\": {\r\n \"describedByEntityStatement\": \"\"\r\n }\r\n }\r\n ],\r\n \"personStatements\": [\r\n {\r\n \"addresses\": [\r\n {\r\n \"city\": \"Edmonton\",\r\n \"country\": \"CA\",\r\n \"countryName\": \"Canada\",\r\n \"postalCode\": \"T6T 1B6\",\r\n \"region\": \"AB\",\r\n \"street\": \"4323 33 St NW\",\r\n \"streetAdditional\": \"\"\r\n }\r\n ],\r\n \"birthDate\": \"2014-11-07\",\r\n \"email\": \"kial@daxiom.com\",\r\n \"hasTaxNumber\": true,\r\n \"identifiers\": [\r\n {\r\n \"id\": \"711 612 325\",\r\n \"scheme\": \"CAN-TAXID\",\r\n \"schemeName\": \"ITN\"\r\n }\r\n ],\r\n \"isComponent\": false,\r\n \"isPermanentResidentCa\": false,\r\n \"names\": [\r\n {\r\n \"fullName\": \"Kial Jinnah\",\r\n \"type\": \"individual\"\r\n }\r\n ],\r\n \"nationalities\": [\r\n {\r\n \"code\": \"CA\",\r\n \"name\": \"Canada\"\r\n }\r\n ],\r\n \"personType\": \"knownPerson\",\r\n \"phoneNumber\": {\r\n \"countryCallingCode\": \"1\",\r\n \"countryCode2letterIso\": \"CA\",\r\n \"number\": \"7783888844\"\r\n },\r\n \"placeOfResidence\": {\r\n \"city\": \"Edmonton\",\r\n \"country\": \"CA\",\r\n \"countryName\": \"Canada\",\r\n \"postalCode\": \"T6T 1B6\",\r\n \"region\": \"AB\",\r\n \"street\": \"4323 33 St NW\",\r\n \"streetAdditional\": \"\"\r\n },\r\n \"publicationDetails\": {\r\n \"bodsVersion\": \"0.3\",\r\n \"publicationDate\": \"2024-09-11\",\r\n \"publisher\": {\r\n \"name\": \"BCROS - BC Registries and Online Services\",\r\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\r\n }\r\n },\r\n \"source\": {\r\n \"assertedBy\": [\r\n {\r\n \"name\": \"Kial Jinnah\"\r\n }\r\n ],\r\n \"description\": \"Using Gov BC - BTR - Web UI\",\r\n \"type\": [\r\n \"selfDeclaration\"\r\n ]\r\n },\r\n \"statementDate\": \"2024-09-11\",\r\n \"statementID\": \"1199dc30-6cd8-47fa-be79-f057348ab36b\",\r\n \"statementType\": \"personStatement\",\r\n \"taxResidencies\": [\r\n {\r\n \"code\": \"CA\",\r\n \"name\": \"Canada\"\r\n }\r\n ],\r\n \"uuid\": \"a4b3844a-f68b-4092-b490-85a603f6d424\"\r\n },\r\n {\r\n \"addresses\": [\r\n {\r\n \"city\": \"Vancouver\",\r\n \"country\": \"CA\",\r\n \"countryName\": \"Canada\",\r\n \"postalCode\": \"V6H 2T8\",\r\n \"region\": \"BC\",\r\n \"street\": \"Th-3023 Birch St\",\r\n \"streetAdditional\": \"\"\r\n }\r\n ],\r\n \"birthDate\": \"2000-02-02\",\r\n \"email\": \"kial@daxiom.com\",\r\n \"hasTaxNumber\": true,\r\n \"identifiers\": [\r\n {\r\n \"id\": \"402 931 299\",\r\n \"scheme\": \"CAN-TAXID\",\r\n \"schemeName\": \"ITN\"\r\n }\r\n ],\r\n \"isComponent\": false,\r\n \"isPermanentResidentCa\": true,\r\n \"names\": [\r\n {\r\n \"fullName\": \"Wallaby Willow\",\r\n \"type\": \"individual\"\r\n }\r\n ],\r\n \"nationalities\": [\r\n {\r\n \"code\": \"AL\",\r\n \"name\": \"Albania\"\r\n },\r\n {\r\n \"code\": \"BZ\",\r\n \"name\": \"Belize\"\r\n }\r\n ],\r\n \"personType\": \"knownPerson\",\r\n \"phoneNumber\": {\r\n \"countryCallingCode\": \"1\",\r\n \"countryCode2letterIso\": \"CA\",\r\n \"number\": \"2508747772\"\r\n },\r\n \"placeOfResidence\": {\r\n \"city\": \"Vancouver\",\r\n \"country\": \"CA\",\r\n \"countryName\": \"Canada\",\r\n \"postalCode\": \"V6H 2T8\",\r\n \"region\": \"BC\",\r\n \"street\": \"Th-3023 Birch St\",\r\n \"streetAdditional\": \"\"\r\n },\r\n \"publicationDetails\": {\r\n \"bodsVersion\": \"0.3\",\r\n \"publicationDate\": \"2024-09-16\",\r\n \"publisher\": {\r\n \"name\": \"BCROS - BC Registries and Online Services\",\r\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\r\n }\r\n },\r\n \"source\": {\r\n \"assertedBy\": [\r\n {\r\n \"name\": \"Wallaby Willow\"\r\n }\r\n ],\r\n \"description\": \"Using Gov BC - BTR - Web UI\",\r\n \"type\": [\r\n \"selfDeclaration\"\r\n ]\r\n },\r\n \"statementDate\": \"2024-09-16\",\r\n \"statementID\": \"4b7863a1-4fbf-42a5-afe8-8f4a4f3ca049\",\r\n \"statementType\": \"personStatement\",\r\n \"taxResidencies\": [\r\n {\r\n \"code\": \"CA\",\r\n \"name\": \"Canada\"\r\n }\r\n ],\r\n \"uuid\": \"1a825cce-a3fa-47b2-b8c3-e2fae40ac7df\"\r\n },\r\n {\r\n \"addresses\": [\r\n {\r\n \"city\": \"Longueuil\",\r\n \"country\": \"CA\",\r\n \"countryName\": \"Canada\",\r\n \"postalCode\": \"J4H 3X9\",\r\n \"region\": \"QC\",\r\n \"street\": \"433-405 Ch De Chambly\",\r\n \"streetAdditional\": \"\"\r\n }\r\n ],\r\n \"birthDate\": \"2005-09-13\",\r\n \"email\": \"kial@daxiom.com\",\r\n \"hasTaxNumber\": false,\r\n \"identifiers\": [],\r\n \"isComponent\": false,\r\n \"isPermanentResidentCa\": false,\r\n \"names\": [\r\n {\r\n \"fullName\": \"Waffles Butter\",\r\n \"type\": \"individual\"\r\n }\r\n ],\r\n \"nationalities\": [\r\n {\r\n \"code\": \"US\",\r\n \"name\": \"United States\"\r\n }\r\n ],\r\n \"personType\": \"knownPerson\",\r\n \"phoneNumber\": {\r\n \"countryCallingCode\": \"1\",\r\n \"countryCode2letterIso\": \"CA\",\r\n \"number\": \"7784467467\"\r\n },\r\n \"placeOfResidence\": {\r\n \"city\": \"Longueuil\",\r\n \"country\": \"CA\",\r\n \"countryName\": \"Canada\",\r\n \"postalCode\": \"J4H 3X9\",\r\n \"region\": \"QC\",\r\n \"street\": \"433-405 Ch De Chambly\",\r\n \"streetAdditional\": \"\"\r\n },\r\n \"publicationDetails\": {\r\n \"bodsVersion\": \"0.3\",\r\n \"publicationDate\": \"2024-09-12\",\r\n \"publisher\": {\r\n \"name\": \"BCROS - BC Registries and Online Services\",\r\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\r\n }\r\n },\r\n \"source\": {\r\n \"assertedBy\": [\r\n {\r\n \"name\": \"Waffles Butter\"\r\n }\r\n ],\r\n \"description\": \"Using Gov BC - BTR - Web UI\",\r\n \"type\": [\r\n \"selfDeclaration\"\r\n ]\r\n },\r\n \"statementDate\": \"2024-09-12\",\r\n \"statementID\": \"ce935e86-f4b9-4938-b12e-29c5e5cc213d\",\r\n \"statementType\": \"personStatement\",\r\n \"taxResidencies\": [],\r\n \"uuid\": \"ce935e86-f4b9-4938-b12e-29c5e5cc213d\"\r\n },\r\n {\r\n \"addresses\": [\r\n {\r\n \"city\": \"Kitchener\",\r\n \"country\": \"CA\",\r\n \"countryName\": \"Canada\",\r\n \"postalCode\": \"N2E 3H6\",\r\n \"region\": \"ON\",\r\n \"street\": \"44-35 Howe Dr\",\r\n \"streetAdditional\": \"\"\r\n }\r\n ],\r\n \"birthDate\": \"2003-09-03\",\r\n \"email\": \"kial@daxiom.com\",\r\n \"hasTaxNumber\": false,\r\n \"identifiers\": [],\r\n \"isComponent\": false,\r\n \"isPermanentResidentCa\": false,\r\n \"names\": [\r\n {\r\n \"fullName\": \"testing tester\",\r\n \"type\": \"individual\"\r\n }\r\n ],\r\n \"nationalities\": [\r\n {\r\n \"code\": \"CA\",\r\n \"name\": \"Canada\"\r\n }\r\n ],\r\n \"personType\": \"knownPerson\",\r\n \"phoneNumber\": {\r\n \"countryCallingCode\": \"1\",\r\n \"countryCode2letterIso\": \"CA\",\r\n \"number\": \"6139585888\"\r\n },\r\n \"placeOfResidence\": {\r\n \"city\": \"Kitchener\",\r\n \"country\": \"CA\",\r\n \"countryName\": \"Canada\",\r\n \"postalCode\": \"N2E 3H6\",\r\n \"region\": \"ON\",\r\n \"street\": \"44-35 Howe Dr\",\r\n \"streetAdditional\": \"\"\r\n },\r\n \"publicationDetails\": {\r\n \"bodsVersion\": \"0.3\",\r\n \"publicationDate\": \"2024-09-19\",\r\n \"publisher\": {\r\n \"name\": \"BCROS - BC Registries and Online Services\",\r\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\r\n }\r\n },\r\n \"source\": {\r\n \"assertedBy\": [\r\n {\r\n \"name\": \"testing tester\"\r\n }\r\n ],\r\n \"description\": \"Using Gov BC - BTR - Web UI\",\r\n \"type\": [\r\n \"selfDeclaration\"\r\n ]\r\n },\r\n \"statementDate\": \"2024-09-19\",\r\n \"statementID\": \"156cbfa3-ec28-42cf-8077-0b512c0b7dac\",\r\n \"statementType\": \"personStatement\",\r\n \"taxResidencies\": [],\r\n \"uuid\": \"6cd3eba0-ab93-42b0-98be-22e8b1397fea\"\r\n }\r\n ]\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{internal_url}}/plots", + "host": [ + "{{internal_url}}" + ], + "path": [ + "plots" + ] + } + }, + "status": "CREATED", + "code": 201, + "_postman_previewlanguage": "json", + "header": [ { - "key": "token", - "value": "{{token}}", - "type": "string" + "key": "Server", + "value": "Werkzeug/3.0.1 Python/3.11.4" + }, + { + "key": "Date", + "value": "Wed, 25 Sep 2024 18:42:27 GMT" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Content-Length", + "value": "10216" + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*" + }, + { + "key": "API", + "value": "btr-api/0.2.1" + }, + { + "key": "Connection", + "value": "close" } - ] - }, - "method": "GET", - "header": [], - "url": { - "raw": "{{internal_url}}/plots", - "host": [ - "{{internal_url}}" ], - "path": [ - "plots" - ] + "cookie": [], + "body": "{\n \"id\": 548628,\n \"payload\": {\n \"businessIdentifier\": \"BC1230113\",\n \"effectiveDate\": \"2024-09-19\",\n \"entityStatement\": {\n \"entityType\": \"legalEntity\",\n \"identifiers\": [],\n \"isComponent\": false,\n \"name\": \"1230113 B.C. LTD.\",\n \"publicationDetails\": {\n \"bodsVersion\": \"0.3\",\n \"publicationDate\": \"2024-09-19\",\n \"publisher\": {\n \"name\": \"BCROS - BC Registries and Online Services\",\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\n }\n },\n \"source\": {\n \"assertedBy\": [\n {\n \"name\": \"BCROS - BC Registries and Online Services\",\n \"uri\": \"https://www.bcregistry.gov.bc.ca/\"\n }\n ],\n \"retrievedAt\": \"2024-09-19T13:59:55.310Z\",\n \"type\": [\n \"officialRegister\",\n \"verified\"\n ]\n },\n \"statementDate\": \"2024-09-19\",\n \"statementID\": \"0df55746-4d87-4500-ba14-f16e4a91bda6\",\n \"statementType\": \"entityStatement\"\n },\n \"noSignificantIndividualsExist\": false,\n \"ownershipOrControlStatements\": [\n {\n \"interestedParty\": {\n \"describedByPersonStatement\": \"ffe16ac2-9092-4a0e-bbdd-9772dece6dc5\"\n },\n \"interests\": [\n {\n \"details\": \"controlType.shares.beneficialOwner\",\n \"directOrIndirect\": \"direct\",\n \"share\": {\n \"exclusiveMaximum\": false,\n \"exclusiveMinimum\": false,\n \"maximum\": 50,\n \"minimum\": 25\n },\n \"startDate\": \"2014-11-07\",\n \"type\": \"shareholding\"\n },\n {\n \"details\": \"controlType.shares.registeredOwner\",\n \"directOrIndirect\": \"direct\",\n \"share\": {\n \"exclusiveMaximum\": false,\n \"exclusiveMinimum\": false,\n \"maximum\": 50,\n \"minimum\": 25\n },\n \"startDate\": \"2014-11-07\",\n \"type\": \"shareholding\"\n },\n {\n \"connectedIndividuals\": [\n {\n \"uuid\": \"04b68a23-a3e9-49fd-83f2-0273dac3b91b\"\n }\n ],\n \"details\": \"controlType.shares.actingJointly\",\n \"directOrIndirect\": \"direct\",\n \"share\": {\n \"exclusiveMaximum\": false,\n \"exclusiveMinimum\": false,\n \"maximum\": 50,\n \"minimum\": 25\n },\n \"startDate\": \"2014-11-07\",\n \"type\": \"shareholding\"\n },\n {\n \"details\": \"controlType.votes.beneficialOwner\",\n \"directOrIndirect\": \"direct\",\n \"share\": {\n \"exclusiveMaximum\": false,\n \"exclusiveMinimum\": true,\n \"maximum\": 75,\n \"minimum\": 50\n },\n \"startDate\": \"2014-11-07\",\n \"type\": \"votingRights\"\n }\n ],\n \"isComponent\": false,\n \"publicationDetails\": {\n \"bodsVersion\": \"0.3\",\n \"publicationDate\": \"2024-09-11\",\n \"publisher\": {\n \"name\": \"BCROS - BC Registries and Online Services\",\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\n }\n },\n \"source\": {\n \"assertedBy\": [\n {\n \"name\": \"Kial Jinnah\"\n }\n ],\n \"description\": \"Using Gov BC - BTR - Web UI\",\n \"type\": [\n \"selfDeclaration\"\n ]\n },\n \"statementDate\": \"2024-09-11\",\n \"statementID\": \"30b62e71-e0a0-481c-be5e-c852dea68761\",\n \"statementType\": \"ownershipOrControlStatement\",\n \"subject\": {\n \"describedByEntityStatement\": \"\"\n }\n },\n {\n \"interestedParty\": {\n \"describedByPersonStatement\": \"a9ae373e-400f-47a5-829a-8c40bc07ad00\"\n },\n \"interests\": [\n {\n \"details\": \"controlType.shares.indirectControl\",\n \"directOrIndirect\": \"indirect\",\n \"share\": {\n \"exclusiveMaximum\": false,\n \"exclusiveMinimum\": false,\n \"maximum\": 50,\n \"minimum\": 25\n },\n \"startDate\": \"2019-09-19\",\n \"type\": \"shareholding\"\n },\n {\n \"details\": \"controlType.votes.registeredOwner\",\n \"directOrIndirect\": \"direct\",\n \"share\": {\n \"exclusiveMaximum\": false,\n \"exclusiveMinimum\": false,\n \"maximum\": 50,\n \"minimum\": 25\n },\n \"startDate\": \"2019-09-19\",\n \"type\": \"votingRights\"\n }\n ],\n \"isComponent\": false,\n \"publicationDetails\": {\n \"bodsVersion\": \"0.3\",\n \"publicationDate\": \"2024-09-16\",\n \"publisher\": {\n \"name\": \"BCROS - BC Registries and Online Services\",\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\n }\n },\n \"source\": {\n \"assertedBy\": [\n {\n \"name\": \"Wallaby Willow\"\n }\n ],\n \"description\": \"Using Gov BC - BTR - Web UI\",\n \"type\": [\n \"selfDeclaration\"\n ]\n },\n \"statementDate\": \"2024-09-16\",\n \"statementID\": \"d30808c3-6693-4e87-aedd-5a4443eb8b07\",\n \"statementType\": \"ownershipOrControlStatement\",\n \"subject\": {\n \"describedByEntityStatement\": \"\"\n }\n },\n {\n \"interestedParty\": {\n \"describedByPersonStatement\": \"04b68a23-a3e9-49fd-83f2-0273dac3b91b\"\n },\n \"interests\": [\n {\n \"details\": \"controlType.shares.indirectControl\",\n \"directOrIndirect\": \"indirect\",\n \"share\": {\n \"exclusiveMaximum\": true,\n \"exclusiveMinimum\": false,\n \"maximum\": 25,\n \"minimum\": 0\n },\n \"startDate\": \"2021-09-07\",\n \"type\": \"shareholding\"\n },\n {\n \"connectedIndividuals\": [\n {\n \"uuid\": \"ffe16ac2-9092-4a0e-bbdd-9772dece6dc5\"\n }\n ],\n \"details\": \"controlType.shares.actingJointly\",\n \"directOrIndirect\": \"direct\",\n \"share\": {\n \"exclusiveMaximum\": false,\n \"exclusiveMinimum\": false,\n \"maximum\": 50,\n \"minimum\": 25\n },\n \"startDate\": \"2014-11-07\",\n \"type\": \"shareholding\"\n }\n ],\n \"isComponent\": false,\n \"publicationDetails\": {\n \"bodsVersion\": \"0.3\",\n \"publicationDate\": \"2024-09-12\",\n \"publisher\": {\n \"name\": \"BCROS - BC Registries and Online Services\",\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\n }\n },\n \"source\": {\n \"assertedBy\": [\n {\n \"name\": \"Waffles Butter\"\n }\n ],\n \"description\": \"Using Gov BC - BTR - Web UI\",\n \"type\": [\n \"selfDeclaration\"\n ]\n },\n \"statementDate\": \"2024-09-12\",\n \"statementID\": \"22d490b3-ce63-4e83-b497-f5c7069b404e\",\n \"statementType\": \"ownershipOrControlStatement\",\n \"subject\": {\n \"describedByEntityStatement\": \"\"\n }\n },\n {\n \"interestedParty\": {\n \"describedByPersonStatement\": \"eadacba2-7d30-4f9d-a3db-cc0f67d1e915\"\n },\n \"interests\": [\n {\n \"details\": \"controlType.shares.registeredOwner\",\n \"directOrIndirect\": \"direct\",\n \"share\": {\n \"exclusiveMaximum\": false,\n \"exclusiveMinimum\": false,\n \"maximum\": 50,\n \"minimum\": 25\n },\n \"startDate\": \"2024-09-02\",\n \"type\": \"shareholding\"\n }\n ],\n \"isComponent\": false,\n \"publicationDetails\": {\n \"bodsVersion\": \"0.3\",\n \"publicationDate\": \"2024-09-19\",\n \"publisher\": {\n \"name\": \"BCROS - BC Registries and Online Services\",\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\n }\n },\n \"source\": {\n \"assertedBy\": [\n {\n \"name\": \"testing tester\"\n }\n ],\n \"description\": \"Using Gov BC - BTR - Web UI\",\n \"type\": [\n \"selfDeclaration\"\n ]\n },\n \"statementDate\": \"2024-09-19\",\n \"statementID\": \"fd25f723-d1a7-48e5-94c8-db1f60445a2a\",\n \"statementType\": \"ownershipOrControlStatement\",\n \"subject\": {\n \"describedByEntityStatement\": \"\"\n }\n }\n ],\n \"personStatements\": [\n {\n \"addresses\": [\n {\n \"city\": \"Edmonton\",\n \"country\": \"CA\",\n \"countryName\": \"Canada\",\n \"postalCode\": \"T6T 1B6\",\n \"region\": \"AB\",\n \"street\": \"4323 33 St NW\",\n \"streetAdditional\": \"\"\n }\n ],\n \"birthDate\": \"2014-11-07\",\n \"email\": \"kial@daxiom.com\",\n \"hasTaxNumber\": true,\n \"identifiers\": [\n {\n \"id\": \"711 612 325\",\n \"scheme\": \"CAN-TAXID\",\n \"schemeName\": \"ITN\"\n }\n ],\n \"isComponent\": false,\n \"isPermanentResidentCa\": false,\n \"names\": [\n {\n \"fullName\": \"Kial Jinnah\",\n \"type\": \"individual\"\n }\n ],\n \"nationalities\": [\n {\n \"code\": \"CA\",\n \"name\": \"Canada\"\n }\n ],\n \"personType\": \"knownPerson\",\n \"phoneNumber\": {\n \"countryCallingCode\": \"1\",\n \"countryCode2letterIso\": \"CA\",\n \"number\": \"7783888844\"\n },\n \"placeOfResidence\": {\n \"city\": \"Edmonton\",\n \"country\": \"CA\",\n \"countryName\": \"Canada\",\n \"postalCode\": \"T6T 1B6\",\n \"region\": \"AB\",\n \"street\": \"4323 33 St NW\",\n \"streetAdditional\": \"\"\n },\n \"publicationDetails\": {\n \"bodsVersion\": \"0.3\",\n \"publicationDate\": \"2024-09-11\",\n \"publisher\": {\n \"name\": \"BCROS - BC Registries and Online Services\",\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\n }\n },\n \"source\": {\n \"assertedBy\": [\n {\n \"name\": \"Kial Jinnah\"\n }\n ],\n \"description\": \"Using Gov BC - BTR - Web UI\",\n \"type\": [\n \"selfDeclaration\"\n ]\n },\n \"statementDate\": \"2024-09-11\",\n \"statementID\": \"ffe16ac2-9092-4a0e-bbdd-9772dece6dc5\",\n \"statementType\": \"personStatement\",\n \"taxResidencies\": [\n {\n \"code\": \"CA\",\n \"name\": \"Canada\"\n }\n ],\n \"uuid\": \"a4b3844a-f68b-4092-b490-85a603f6d424\"\n },\n {\n \"addresses\": [\n {\n \"city\": \"Vancouver\",\n \"country\": \"CA\",\n \"countryName\": \"Canada\",\n \"postalCode\": \"V6H 2T8\",\n \"region\": \"BC\",\n \"street\": \"Th-3023 Birch St\",\n \"streetAdditional\": \"\"\n }\n ],\n \"birthDate\": \"2000-02-02\",\n \"email\": \"kial@daxiom.com\",\n \"hasTaxNumber\": true,\n \"identifiers\": [\n {\n \"id\": \"402 931 299\",\n \"scheme\": \"CAN-TAXID\",\n \"schemeName\": \"ITN\"\n }\n ],\n \"isComponent\": false,\n \"isPermanentResidentCa\": true,\n \"names\": [\n {\n \"fullName\": \"Wallaby Willow\",\n \"type\": \"individual\"\n }\n ],\n \"nationalities\": [\n {\n \"code\": \"AL\",\n \"name\": \"Albania\"\n },\n {\n \"code\": \"BZ\",\n \"name\": \"Belize\"\n }\n ],\n \"personType\": \"knownPerson\",\n \"phoneNumber\": {\n \"countryCallingCode\": \"1\",\n \"countryCode2letterIso\": \"CA\",\n \"number\": \"2508747772\"\n },\n \"placeOfResidence\": {\n \"city\": \"Vancouver\",\n \"country\": \"CA\",\n \"countryName\": \"Canada\",\n \"postalCode\": \"V6H 2T8\",\n \"region\": \"BC\",\n \"street\": \"Th-3023 Birch St\",\n \"streetAdditional\": \"\"\n },\n \"publicationDetails\": {\n \"bodsVersion\": \"0.3\",\n \"publicationDate\": \"2024-09-16\",\n \"publisher\": {\n \"name\": \"BCROS - BC Registries and Online Services\",\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\n }\n },\n \"source\": {\n \"assertedBy\": [\n {\n \"name\": \"Wallaby Willow\"\n }\n ],\n \"description\": \"Using Gov BC - BTR - Web UI\",\n \"type\": [\n \"selfDeclaration\"\n ]\n },\n \"statementDate\": \"2024-09-16\",\n \"statementID\": \"a9ae373e-400f-47a5-829a-8c40bc07ad00\",\n \"statementType\": \"personStatement\",\n \"taxResidencies\": [\n {\n \"code\": \"CA\",\n \"name\": \"Canada\"\n }\n ],\n \"uuid\": \"1a825cce-a3fa-47b2-b8c3-e2fae40ac7df\"\n },\n {\n \"addresses\": [\n {\n \"city\": \"Longueuil\",\n \"country\": \"CA\",\n \"countryName\": \"Canada\",\n \"postalCode\": \"J4H 3X9\",\n \"region\": \"QC\",\n \"street\": \"433-405 Ch De Chambly\",\n \"streetAdditional\": \"\"\n }\n ],\n \"birthDate\": \"2005-09-13\",\n \"email\": \"kial@daxiom.com\",\n \"hasTaxNumber\": false,\n \"identifiers\": [],\n \"isComponent\": false,\n \"isPermanentResidentCa\": false,\n \"names\": [\n {\n \"fullName\": \"Waffles Butter\",\n \"type\": \"individual\"\n }\n ],\n \"nationalities\": [\n {\n \"code\": \"US\",\n \"name\": \"United States\"\n }\n ],\n \"personType\": \"knownPerson\",\n \"phoneNumber\": {\n \"countryCallingCode\": \"1\",\n \"countryCode2letterIso\": \"CA\",\n \"number\": \"7784467467\"\n },\n \"placeOfResidence\": {\n \"city\": \"Longueuil\",\n \"country\": \"CA\",\n \"countryName\": \"Canada\",\n \"postalCode\": \"J4H 3X9\",\n \"region\": \"QC\",\n \"street\": \"433-405 Ch De Chambly\",\n \"streetAdditional\": \"\"\n },\n \"publicationDetails\": {\n \"bodsVersion\": \"0.3\",\n \"publicationDate\": \"2024-09-12\",\n \"publisher\": {\n \"name\": \"BCROS - BC Registries and Online Services\",\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\n }\n },\n \"source\": {\n \"assertedBy\": [\n {\n \"name\": \"Waffles Butter\"\n }\n ],\n \"description\": \"Using Gov BC - BTR - Web UI\",\n \"type\": [\n \"selfDeclaration\"\n ]\n },\n \"statementDate\": \"2024-09-12\",\n \"statementID\": \"04b68a23-a3e9-49fd-83f2-0273dac3b91b\",\n \"statementType\": \"personStatement\",\n \"taxResidencies\": [],\n \"uuid\": \"ce935e86-f4b9-4938-b12e-29c5e5cc213d\"\n },\n {\n \"addresses\": [\n {\n \"city\": \"Kitchener\",\n \"country\": \"CA\",\n \"countryName\": \"Canada\",\n \"postalCode\": \"N2E 3H6\",\n \"region\": \"ON\",\n \"street\": \"44-35 Howe Dr\",\n \"streetAdditional\": \"\"\n }\n ],\n \"birthDate\": \"2003-09-03\",\n \"email\": \"kial@daxiom.com\",\n \"hasTaxNumber\": false,\n \"identifiers\": [],\n \"isComponent\": false,\n \"isPermanentResidentCa\": false,\n \"names\": [\n {\n \"fullName\": \"testing tester\",\n \"type\": \"individual\"\n }\n ],\n \"nationalities\": [\n {\n \"code\": \"CA\",\n \"name\": \"Canada\"\n }\n ],\n \"personType\": \"knownPerson\",\n \"phoneNumber\": {\n \"countryCallingCode\": \"1\",\n \"countryCode2letterIso\": \"CA\",\n \"number\": \"6139585888\"\n },\n \"placeOfResidence\": {\n \"city\": \"Kitchener\",\n \"country\": \"CA\",\n \"countryName\": \"Canada\",\n \"postalCode\": \"N2E 3H6\",\n \"region\": \"ON\",\n \"street\": \"44-35 Howe Dr\",\n \"streetAdditional\": \"\"\n },\n \"publicationDetails\": {\n \"bodsVersion\": \"0.3\",\n \"publicationDate\": \"2024-09-19\",\n \"publisher\": {\n \"name\": \"BCROS - BC Registries and Online Services\",\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\n }\n },\n \"source\": {\n \"assertedBy\": [\n {\n \"name\": \"testing tester\"\n }\n ],\n \"description\": \"Using Gov BC - BTR - Web UI\",\n \"type\": [\n \"selfDeclaration\"\n ]\n },\n \"statementDate\": \"2024-09-19\",\n \"statementID\": \"eadacba2-7d30-4f9d-a3db-cc0f67d1e915\",\n \"statementType\": \"personStatement\",\n \"taxResidencies\": [],\n \"uuid\": \"6cd3eba0-ab93-42b0-98be-22e8b1397fea\"\n }\n ]\n },\n \"submittedDatetime\": \"2024-09-25T14:42:25.071673\",\n \"submitterId\": 7,\n \"type\": \"other\"\n}" } - }, - "response": [] + ] }, { - "name": "get latest for entity", + "name": "update", "request": { "auth": { "type": "bearer", @@ -250,22 +292,36 @@ } ] }, - "method": "GET", - "header": [], + "method": "PUT", + "header": [ + { + "key": "account-id", + "value": "{{account_id}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"businessIdentifier\":\"BC1230113\",\n \"effectiveDate\":\"2024-09-20\",\n \"entityStatement\": {\n \"entityType\": \"legalEntity\",\n \"identifiers\": [],\n \"isComponent\": false,\n \"name\": \"1230113 B.C. LTD.\",\n \"publicationDetails\": {\n \"bodsVersion\": \"0.3\",\n \"publicationDate\": \"2024-09-19\",\n \"publisher\": {\n \"name\": \"BCROS - BC Registries and Online Services\",\n \"url\": \"https://www.bcregistry.gov.bc.ca/\"\n }\n },\n \"source\": {\n \"assertedBy\": [\n {\n \"name\": \"BCROS - BC Registries and Online Services\",\n \"uri\": \"https://www.bcregistry.gov.bc.ca/\"\n }\n ],\n \"retrievedAt\": \"2024-09-19T13:59:55.310Z\",\n \"type\": [\n \"officialRegister\",\n \"verified\"\n ]\n },\n \"statementDate\": \"2024-09-19\",\n \"statementID\": \"0df55746-4d87-4500-ba14-f16e4a91bda6\",\n \"statementType\": \"entityStatement\"\n },\n \"noSignificantIndividualsExist\": false,\n \"ownershipOrControlStatements\":[\n {\n \"statementID\":\"b6f46f1f-5571-4b88-bf91-882e8f094b10\",\n \"interests\":[\n {\n \"directOrIndirect\":\"direct\",\n \"details\":\"controlType.shares.beneficialOwner\",\n \"type\":\"shareholding\",\n \"startDate\":\"2014-11-07\",\n \"share\":{\n \"exclusiveMinimum\":false,\n \"exclusiveMaximum\":false,\n \"minimum\":50,\n \"maximum\":75\n }\n }\n ],\n \"interestedParty\":{\"describedByPersonStatement\":\"7737088a-31be-4461-862c-a0be5773247b\"},\n \"statementDate\":\"2024-09-20\",\n \"statementType\": \"ownershipOrControlStatement\"\n }\n ],\n \"personStatements\":[\n {\n \"placeOfResidence\": {\n \"region\": \"BC\"\n },\n \"birthDate\":\"2012-09-05\",\n \"statementDate\":\"2024-09-20\",\n \"statementID\":\"7737088a-31be-4461-862c-a0be5773247b\"\n }\n ]\n}\n", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { - "raw": "{{internal_url}}/plots/entity/:identifier", + "raw": "{{internal_url}}/plots/:identifier", "host": [ "{{internal_url}}" ], "path": [ "plots", - "entity", ":identifier" ], "variable": [ { "key": "identifier", - "value": "12345" + "value": "548615" } ] } @@ -273,7 +329,7 @@ "response": [] }, { - "name": "update", + "name": "receipt", "request": { "auth": { "type": "bearer", @@ -285,17 +341,16 @@ } ] }, - "method": "PUT", + "method": "POST", "header": [ { - "key": "account-id", - "value": "3113", - "type": "text" + "key": "Account-Id", + "value": "{{staff_account_id}}" } ], "body": { "mode": "raw", - "raw": "{\n \"personStatements\": [\n {\n \"uuid\": \"84d36ff4-c002-42fa-bfcd-e3cca9d01ef2\",\n \"names\": [\n {\n \"fullName\": \"updated tester\",\n \"type\": \"individual\"\n }\n ]\n }\n ]\n}\n", + "raw": "{\n \"corpName\": \"test corpName\",\n \"filingDateTime\": \"2024-01-25\",\n \"filingIdentifier\": \"1\",\n \"businessNumber\": \"123\"\n}", "options": { "raw": { "language": "json" @@ -303,18 +358,20 @@ } }, "url": { - "raw": "{{internal_url}}/plots/:identifier", + "raw": "{{pay_api_url}}/payment-requests/:invoice_id/receipts", "host": [ - "{{internal_url}}" + "{{pay_api_url}}" ], "path": [ - "plots", - ":identifier" + "payment-requests", + ":invoice_id", + "receipts" ], "variable": [ { - "key": "identifier", - "value": "1" + "key": "invoice_id", + "value": "33600", + "description": "invoice id in pay db" } ] } @@ -333,7 +390,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"businessIdentifier\": \"BC1230113\"\n}\n", + "raw": "{\n \"businessIdentifier\": \"BC1144568\"\n}\n", "options": { "raw": { "language": "json" diff --git a/btr-api/tests/unit/models/test_submission.py b/btr-api/tests/unit/models/test_submission.py index cb174548..615a986e 100644 --- a/btr-api/tests/unit/models/test_submission.py +++ b/btr-api/tests/unit/models/test_submission.py @@ -1,12 +1,24 @@ +import pytest + from sqlalchemy import text +from btr_api.models import User from btr_api.models.submission import Submission, SubmissionType +from tests.unit.models.test_user import sample_user +from tests.unit.utils.db_helpers import clear_db + -def test_find_by_id(session): +def test_find_by_id(session, sample_user): # Prepare data - session.execute(text('delete from submission')) - submission = Submission(type=SubmissionType.other, business_identifier="Test identifier") + clear_db(session) + submission = Submission(type=SubmissionType.other, + submitted_payload={ + 'businessIdentifier': 'BC1234567', + 'personStatements': [], + 'ownershipOrControlStatements': [] + }, + submitter=sample_user) session.add(submission) session.commit() @@ -17,11 +29,24 @@ def test_find_by_id(session): assert result == submission -def test_get_filtered_submissions(session): +def test_get_filtered_submissions(session, sample_user): # Prepare data - session.execute(text('delete from submission')) - session.add_all([Submission(type=SubmissionType.other, business_identifier="Test identifier"), - Submission(type=SubmissionType.standard, business_identifier="Another identifier")]) + clear_db(session) + session.add_all([Submission(type=SubmissionType.other, + submitted_payload={ + 'businessIdentifier': 'Test identifier', + 'personStatements': [], + 'ownershipOrControlStatements': [] + }, + submitter=sample_user), + Submission(type=SubmissionType.standard, + submitted_payload={ + 'businessIdentifier': 'Another identifier', + 'personStatements': [], + 'ownershipOrControlStatements': [] + }, + submitter=sample_user) + ]) session.commit() all_submissions = Submission.query.all() # Do test @@ -31,10 +56,16 @@ def test_get_filtered_submissions(session): assert len(result) == len(all_submissions) -def test_save_to_session(session): +def test_save_to_session(session, sample_user): # Prepare data - session.execute(text('delete from submission')) - submission = Submission(type=SubmissionType.other, business_identifier="Test identifier") + clear_db(session) + submission = Submission(type=SubmissionType.other, + submitted_payload={ + 'businessIdentifier': 'BC1234567', + 'personStatements': [], + 'ownershipOrControlStatements': [] + }, + submitter=sample_user) # Do test submission.save_to_session() diff --git a/btr-api/tests/unit/models/test_user.py b/btr-api/tests/unit/models/test_user.py index 84ea1c86..b76c536c 100644 --- a/btr-api/tests/unit/models/test_user.py +++ b/btr-api/tests/unit/models/test_user.py @@ -1,10 +1,12 @@ +from __future__ import annotations + import datetime -from unittest import mock import pytest -from btr_api.config import Testing -from btr_api.models.user import User, db +from btr_api.models import User + +from tests.unit.utils.db_helpers import clear_db @pytest.fixture @@ -24,7 +26,8 @@ def test_display_name_without_name(): assert sample_user.display_name == "testUserName" -def test_find_by_id(client, session, sample_user): +def test_find_by_id(session, sample_user): + clear_db(session) session.add(sample_user) session.commit() @@ -42,21 +45,19 @@ def test_find_by_username(client, session, sample_user): assert result.username == sample_user.username -def test_find_by_sub(client, session, sample_user): - # session.add(sample_user) - # session.commit() - +def test_find_by_sub(sample_user): result = User.find_by_sub(sample_user.sub) - assert result.sub == sample_user.sub -def test_create_from_jwt_token(client, session,sample_user): +def test_create_from_jwt_token(session): + clear_db(session) sample_token = { "iss": "test", "sub": f"subTest{datetime.datetime.now().strftime('%Y%m%d%H%M')}", "idp_userid": "testUserID", "loginSource": "testLogin", + "username": "test" } result = User.create_from_jwt_token(sample_token) @@ -64,7 +65,7 @@ def test_create_from_jwt_token(client, session,sample_user): assert result.login_source == sample_token['loginSource'] -def test_save(client, session, sample_user): +def test_save(session, sample_user): u1 = User.find_by_username(sample_user.username) if not u1: session.add(sample_user) @@ -75,8 +76,3 @@ def test_save(client, session, sample_user): result = User.find_by_username("totallyNewOne") assert result assert result.username == "totallyNewOne" - - -def test_delete(sample_user): - result = sample_user.delete() - assert result == sample_user diff --git a/btr-api/tests/unit/resources/test_submissions.py b/btr-api/tests/unit/resources/test_submissions.py index 71ebfb44..a6368b60 100644 --- a/btr-api/tests/unit/resources/test_submissions.py +++ b/btr-api/tests/unit/resources/test_submissions.py @@ -8,17 +8,20 @@ import pytest import requests from dateutil.relativedelta import relativedelta -from sqlalchemy import text from btr_api.enums import UserType from btr_api.models import Submission as SubmissionModel from btr_api.models import SubmissionType from btr_api.models import User as UserModel +from btr_api.models.submission import SubmissionSerializer from btr_api.services import SubmissionService +from btr_api.services.auth import auth_cache from btr_api.utils import redact_information from tests.unit import nested_session +from tests.unit.models.test_user import sample_user from tests.unit.utils import create_header +from tests.unit.utils.db_helpers import clear_db mocked_entity_response = {'business': {'adminFreeze': False, 'state': 'ACTIVE', 'legalName': 'Mocked Business', 'identifier': 'BC1234567'}} mocked_entity_address_response = { @@ -28,7 +31,6 @@ helper_people = [ { - 'uuid': '2c5fd9bc-2ff1-4545-86aa-d1c02705f4cd', 'email': 'test@test.com', 'names': [{'type': 'individual', 'fullName': 'Test Test'}, {'type': 'alternative', 'fullName': 'tset tset'}], 'phoneNumber': {'number': '5555555555', 'countryCallingCode': '1', 'countryCode2letterIso': 'CA'}, @@ -56,6 +58,7 @@ }, 'birthDate': '1988-01-01', 'identifiers': [{'id': '999 555 444', 'scheme': 'CAN-TAXID', 'schemeName': 'ITN'}], + 'statementID': '1' } ] @@ -107,32 +110,32 @@ def verify_text_in_email(text: str): @pytest.mark.parametrize( 'test_name, submission_type, payload, user_type', - [('simple json', SubmissionType.other, {'racoondog': 'red'}, UserType.USER_COMPETENT_AUTHORITY)], + [('simple json', SubmissionType.other, {'businessIdentifier': 'identifier0'}, UserType.USER_COMPETENT_AUTHORITY)], ) -def test_get_plots(app, client, session, jwt, requests_mock, test_name, submission_type, payload, user_type): +def test_get_plots(app, client, session, jwt, requests_mock, sample_user, test_name, submission_type, payload, user_type): """Get the plot submissions. A parameterized set of tests that runs defined scenarios. """ with nested_session(session): - session.execute(text('delete from submission')) + clear_db(session) # Setup id = '' if payload: sub = SubmissionModel() - sub.business_identifier = 'identifier0' - sub.payload = payload + sub.submitted_payload = payload sub.type = submission_type + sub.submitter = sample_user session.add(sub) session.commit() id = sub.id requests_mock.get( - f'{app.config.get("AUTH_SVC_URL")}/entities/{sub.business_identifier}/authorizations', + f"{app.config.get('AUTH_SVC_URL')}/entities/{sub.business_identifier}/authorizations", json={'orgMembership': 'COORDINATOR', 'roles': ['edit', 'view']}, ) requests_mock.get( - f'{app.config.get("AUTH_SVC_URL")}/orgs/1/products?include_hidden=true', + f"{app.config.get('AUTH_SVC_URL')}/orgs/1/products?include_hidden=true", json=[{'code': 'CA_SEARCH', 'subscriptionStatus': 'ACTIVE'}]) # Test rv = client.get( @@ -172,6 +175,7 @@ def test_get_plots_auth( session, jwt, requests_mock, + sample_user, test_name, business_identifier, auth_svc_response, @@ -180,22 +184,22 @@ def test_get_plots_auth( ): """Test scenarios connected to authentication on plots endpoint.""" with nested_session(session): - session.execute(text('delete from submission')) + clear_db(session) # Setup sub = SubmissionModel() - sub.business_identifier = 'identifier0' - sub.payload = {'racoondog': 'red'} + sub.submitted_payload = {'businessIdentifier': business_identifier} sub.type = SubmissionType.other + sub.submitter = sample_user session.add(sub) session.commit() search_id = sub.id requests_mock.get( - f'{app.config.get("AUTH_SVC_URL")}/entities/{business_identifier}/authorizations', json=auth_svc_response + f"{app.config.get('AUTH_SVC_URL')}/entities/{business_identifier}/authorizations", json=auth_svc_response ) requests_mock.get( - f'{app.config.get("AUTH_SVC_URL")}/orgs/1/products?include_hidden=true', + f"{app.config.get('AUTH_SVC_URL')}/orgs/1/products?include_hidden=true", json=[{'code': 'CA_SEARCH', 'subscriptionStatus': 'ACTIVE'}]) headers = create_header( @@ -224,6 +228,11 @@ def test_post_plots_db_mocked(app, session, client, jwt, mocker, requests_mock): current_dir = os.path.dirname(__file__) mock_user_save = mocker.patch.object(UserModel, 'save') mock_submission_save = mocker.patch.object(SubmissionModel, 'save') + + def mocked_to_dict(submission: SubmissionModel): + return {'id': 123, 'payload': {}} + mocker.patch.object(SubmissionSerializer, 'to_dict', mocked_to_dict) + with open(os.path.join(current_dir, '..', '..', 'mocks', 'significantIndividualsFiling', 'valid.json')) as file: json_data = json.load(file) @@ -291,15 +300,6 @@ def test_post_plots_db_mocked(app, session, client, jwt, mocker, requests_mock): 'statementType': 'ownershipOrControlStatement', 'subject': {'describedByEntityStatement': ''} }, - {'interestedParty': {'describedByPersonStatement': 'ce935e86-f4b9-4938-b12e-29c5e5cc213d', 'addresses': [{'city': 'Longueuil', 'country': 'CA', 'countryName': 'Canada', 'postalCode': 'J4H 3X9', 'region': 'QC', 'street': '433-405 Ch De Chambly', 'streetAdditional': ''}], 'birthDate': '2005-09-13', 'email': 'fake3@email.com', 'hasTaxNumber': False, 'identifiers': [], 'isComponent': False, 'isPermanentResidentCa': False, 'names': [{'fullName': 'Waffles Butter', 'type': 'individual'}], 'nationalities': [{'code': 'US', 'name': 'United States'}], 'personType': 'knownPerson', 'phoneNumber': {'countryCallingCode': '1', 'countryCode2letterIso': 'CA', 'number': '7784467467'}, 'placeOfResidence': {'city': 'Longueuil', 'country': 'CA', 'countryName': 'Canada', 'postalCode': 'J4H 3X9', 'region': 'QC', 'street': '433-405 Ch De Chambly', 'streetAdditional': ''}, 'publicationDetails': {'bodsVersion': '0.3', 'publicationDate': '2024-09-12', 'publisher': {'name': 'BCROS - BC Registries and Online Services', 'url': 'https://www.bcregistry.gov.bc.ca/'}}, 'source': {'assertedBy': [{'name': 'Waffles Butter'}], 'description': 'Using Gov BC - BTR - Web UI', 'type': ['selfDeclaration']}, 'statementDate': '2024-09-12', 'statementID': 'ce935e86-f4b9-4938-b12e-29c5e5cc213d', 'statementType': 'personStatement', 'taxResidencies': [], 'uuid': '839e35b8-d536-42e6-82ba-ba3c5a13582d'}, - 'interests': [{'details': 'controlType.shares.indirectControl', 'directOrIndirect': 'indirect', 'share': {'exclusiveMaximum': True, 'exclusiveMinimum': False, 'maximum': 25, 'minimum': 0}, 'startDate': '2021-09-07', 'type': 'shareholding'}], - 'isComponent': False, - 'publicationDetails': {'bodsVersion': '0.3', 'publicationDate': '2024-09-12', 'publisher': {'name': 'BCROS - BC Registries and Online Services', 'url': 'https://www.bcregistry.gov.bc.ca/'}}, - 'source': {'assertedBy': [{'name': 'Waffles Butter'}], 'description': 'Using Gov BC - BTR - Web UI', 'type': ['selfDeclaration']}, - 'statementDate': '2024-09-12', - 'statementID': 'f4d9f29b-559b-4353-9bd4-89ada5ae5209', - 'statementType': 'ownershipOrControlStatement', - 'subject': {'describedByEntityStatement': ''}}, {'interestedParty': {'describedByPersonStatement': '4b7863a1-4fbf-42a5-afe8-8f4a4f3ca049', 'addresses': [{'city': 'Vancouver', 'country': 'CA', 'countryName': 'Canada', 'postalCode': 'V6H 2T8', 'region': 'BC', 'street': 'Th-3023 Birch St', 'streetAdditional': ''}], 'birthDate': '2000-02-02', 'email': 'fake2@email.com', 'hasTaxNumber': True, 'identifiers': [{'id': '402 931 299', 'scheme': 'CAN-TAXID', 'schemeName': 'ITN'}], 'isComponent': False, 'isPermanentResidentCa': True, 'names': [{'fullName': 'Wallaby Willow', 'type': 'individual'}], 'nationalities': [{'code': 'AL', 'name': 'Albania'}, {'code': 'BZ', 'name': 'Belize'}], 'personType': 'knownPerson', 'phoneNumber': {'countryCallingCode': '1', 'countryCode2letterIso': 'CA', 'number': '2508747772'}, 'placeOfResidence': {'city': 'Vancouver', 'country': 'CA', 'countryName': 'Canada', 'postalCode': 'V6H 2T8', 'region': 'BC', 'street': 'Th-3023 Birch St', 'streetAdditional': ''}, 'publicationDetails': {'bodsVersion': '0.3', 'publicationDate': '2024-09-16', 'publisher': {'name': 'BCROS - BC Registries and Online Services', 'url': 'https://www.bcregistry.gov.bc.ca/'}}, 'source': {'assertedBy': [{'name': 'Wallaby Willow'}], 'description': 'Using Gov BC - BTR - Web UI', 'type': ['selfDeclaration']}, 'statementDate': '2024-09-16', 'statementID': '4b7863a1-4fbf-42a5-afe8-8f4a4f3ca049', 'statementType': 'personStatement', 'taxResidencies': [{'code': 'CA', 'name': 'Canada'}], 'uuid': '1a825cce-a3fa-47b2-b8c3-e2fae40ac7df'}, 'interests': [{'details': 'controlType.shares.indirectControl', 'directOrIndirect': 'indirect', 'share': {'exclusiveMaximum': False, 'exclusiveMinimum': False, 'maximum': 50, 'minimum': 25}, 'startDate': '2019-09-19', 'type': 'shareholding'}, {'details': 'controlType.votes.registeredOwner', 'directOrIndirect': 'direct', 'share': {'exclusiveMaximum': False, 'exclusiveMinimum': False, 'maximum': 50, 'minimum': 25}, 'startDate': '2019-09-19', 'type': 'votingRights'}], 'isComponent': False, @@ -309,6 +309,15 @@ def test_post_plots_db_mocked(app, session, client, jwt, mocker, requests_mock): 'statementID': 'aef71bd1-8c64-4fff-a2d5-9a25b450745d', 'statementType': 'ownershipOrControlStatement', 'subject': {'describedByEntityStatement': ''}}, + {'interestedParty': {'describedByPersonStatement': 'ce935e86-f4b9-4938-b12e-29c5e5cc213d', 'addresses': [{'city': 'Longueuil', 'country': 'CA', 'countryName': 'Canada', 'postalCode': 'J4H 3X9', 'region': 'QC', 'street': '433-405 Ch De Chambly', 'streetAdditional': ''}], 'birthDate': '2005-09-13', 'email': 'fake3@email.com', 'hasTaxNumber': False, 'identifiers': [], 'isComponent': False, 'isPermanentResidentCa': False, 'names': [{'fullName': 'Waffles Butter', 'type': 'individual'}], 'nationalities': [{'code': 'US', 'name': 'United States'}], 'personType': 'knownPerson', 'phoneNumber': {'countryCallingCode': '1', 'countryCode2letterIso': 'CA', 'number': '7784467467'}, 'placeOfResidence': {'city': 'Longueuil', 'country': 'CA', 'countryName': 'Canada', 'postalCode': 'J4H 3X9', 'region': 'QC', 'street': '433-405 Ch De Chambly', 'streetAdditional': ''}, 'publicationDetails': {'bodsVersion': '0.3', 'publicationDate': '2024-09-12', 'publisher': {'name': 'BCROS - BC Registries and Online Services', 'url': 'https://www.bcregistry.gov.bc.ca/'}}, 'source': {'assertedBy': [{'name': 'Waffles Butter'}], 'description': 'Using Gov BC - BTR - Web UI', 'type': ['selfDeclaration']}, 'statementDate': '2024-09-12', 'statementID': 'ce935e86-f4b9-4938-b12e-29c5e5cc213d', 'statementType': 'personStatement', 'taxResidencies': [], 'uuid': '839e35b8-d536-42e6-82ba-ba3c5a13582d'}, + 'interests': [{'details': 'controlType.shares.indirectControl', 'directOrIndirect': 'indirect', 'share': {'exclusiveMaximum': True, 'exclusiveMinimum': False, 'maximum': 25, 'minimum': 0}, 'startDate': '2021-09-07', 'type': 'shareholding'}], + 'isComponent': False, + 'publicationDetails': {'bodsVersion': '0.3', 'publicationDate': '2024-09-12', 'publisher': {'name': 'BCROS - BC Registries and Online Services', 'url': 'https://www.bcregistry.gov.bc.ca/'}}, + 'source': {'assertedBy': [{'name': 'Waffles Butter'}], 'description': 'Using Gov BC - BTR - Web UI', 'type': ['selfDeclaration']}, + 'statementDate': '2024-09-12', + 'statementID': 'f4d9f29b-559b-4353-9bd4-89ada5ae5209', + 'statementType': 'ownershipOrControlStatement', + 'subject': {'describedByEntityStatement': ''}}, {'interestedParty': {'describedByPersonStatement': 'e935e86-f4b9-4938-b12e-29c5e5cc213e', 'addresses': [{'city': 'Longueuil', 'country': 'CA', 'countryName': 'Canada', 'postalCode': 'J4H 3X9', 'region': 'QC', 'street': '433-405 Ch De Chambly', 'streetAdditional': ''}], 'email': 'fake4@email.com', 'hasTaxNumber': False, 'identifiers': [], 'isComponent': False, 'isPermanentResidentCa': False, 'missingInfoReason': 'Test person with no birth date', 'names': [{'fullName': 'Tester MissingInfoBday', 'type': 'individual'}], 'personType': 'knownPerson', 'placeOfResidence': {'city': 'Longueuil', 'country': 'CA', 'countryName': 'Canada', 'postalCode': 'J4H 3X9', 'region': 'QC', 'street': '433-405 Ch De Chambly', 'streetAdditional': ''}, 'publicationDetails': {'bodsVersion': '0.3', 'publicationDate': '2024-09-12', 'publisher': {'name': 'BCROS - BC Registries and Online Services', 'url': 'https://www.bcregistry.gov.bc.ca/'}}, 'source': {'assertedBy': [{'name': 'Waffles Butter'}], 'description': 'Using Gov BC - BTR - Web UI', 'type': ['selfDeclaration']}, 'statementDate': '2024-09-12', 'statementID': 'e935e86-f4b9-4938-b12e-29c5e5cc213e', 'statementType': 'personStatement', 'taxResidencies': [], 'uuid': '939e35b8-d536-42e6-82ba-ba3c5a13582z'}, 'interests': [], 'isComponent': False, 'publicationDetails': {'bodsVersion': '0.3', 'publicationDate': '2024-09-12', 'publisher': {'name': 'BCROS - BC Registries and Online Services', 'url': 'https://www.bcregistry.gov.bc.ca/'}}, @@ -364,6 +373,8 @@ def test_post_plots(app, client, session, jwt, requests_mock): ) with nested_session(session): + auth_cache.clear() + clear_db(session) mocked_username = 'wibbly wabble' rv = client.post( '/plots', @@ -433,7 +444,10 @@ def test_put_plots(app, client, session, jwt, requests_mock): ) with nested_session(session): + auth_cache.clear() + clear_db(session) mocked_username = 'wibbly wabble' + print(json_data['businessIdentifier']) rv = client.post( '/plots', json=json_data, @@ -448,20 +462,61 @@ def test_put_plots(app, client, session, jwt, requests_mock): assert rv.status_code == HTTPStatus.CREATED submission_id = rv.json.get('id') assert submission_id - url = f"/plots/{submission_id}" + assert rv.json.get('payload') + assert rv.json['payload'].get('personStatements') + assert rv.json['payload'].get('ownershipOrControlStatements') + # need to get api generated statement ids + url = f'/plots/{submission_id}' + person_stmnt_id = rv.json['payload']['personStatements'][0]['statementID'] + ownership_stmnt_id = rv.json['payload']['ownershipOrControlStatements'][0]['statementID'] put_data = { + 'businessIdentifier': identifier, + 'effectiveDate': '2024-09-23', + 'entityStatement': { + 'entityType': 'legalEntity', + 'identifiers': [], + 'isComponent': False, + 'name': '1230113 B.C. LTD.', + 'publicationDetails': { + 'bodsVersion': '0.3', + 'publicationDate': '2024-09-19', + 'publisher': { + 'name': 'BCROS - BC Registries and Online Services', + 'url': 'https://www.bcregistry.gov.bc.ca/' + } + }, + 'source': { + 'assertedBy': [ + { + 'name': 'BCROS - BC Registries and Online Services', + 'uri': 'https://www.bcregistry.gov.bc.ca/' + } + ], + 'retrievedAt': '2024-09-19T13:59:55.310Z', + 'type': [ + 'officialRegister', + 'verified' + ] + }, + 'statementDate': '2024-09-19', + 'statementID': '0df55746-4d87-4500-ba14-f16e4a91bda6', + 'statementType': 'entityStatement' + }, + 'noSignificantIndividualsExist': False, + 'ownershipOrControlStatements': [{ + 'statementID': ownership_stmnt_id, + 'interestedParty': {'describedByPersonStatement': person_stmnt_id} + }], 'personStatements': [{ - 'uuid': json_data['personStatements'][0]['uuid'], - 'statementID': json_data['personStatements'][0]['statementID'], + 'statementID': person_stmnt_id, 'names': [ { - "type": "individual", - "fullName": "Full2 Name2" + 'type': 'individual', + 'fullName': 'Full2 Name2' } ] }] } - rv = client.put( url, json=put_data, @@ -483,9 +538,8 @@ def test_put_plots(app, client, session, jwt, requests_mock): # check pay link assert updated_submission.invoice_id == mocked_invoice_id - #Check name changed - assert updated_submission.payload['personStatements'][0]['names'][0]['fullName'] == json_data['personStatements'][0]['names'][0]['fullName'] - assert updated_submission.payload['entityStatement'] == json_data['entityStatement'] + # Check name changed + assert updated_submission.person_statements_json[0]['names'][0]['fullName'] == json_data['personStatements'][0]['names'][0]['fullName'] # post submission things all triggered assert legal_api_entity_mock.called == True @@ -532,6 +586,8 @@ def test_post_plots_pay_error(app, client, session, jwt, requests_mock): ) with nested_session(session): + auth_cache.clear() + clear_db(session) rv = client.post( '/plots', json=json_data, @@ -560,7 +616,7 @@ def test_post_plots_pay_error(app, client, session, jwt, requests_mock): def test_post_plots_auth_error(app, client, session, jwt, requests_mock): - """Assure post submission works (auth get token error).""" + """Assure post submission fails with (auth get token error).""" pay_api_mock = requests_mock.post(f"{app.config.get('PAYMENT_SVC_URL')}/payment-requests", json={'id': 1234}) auth_mock = requests_mock.post(app.config.get('SSO_SVC_TOKEN_URL'), exc=requests.exceptions.ConnectTimeout) bor_api_mock = requests_mock.put(f"{app.config.get('BOR_SVC_URL')}/internal/solr/update", json={}) @@ -588,6 +644,8 @@ def test_post_plots_auth_error(app, client, session, jwt, requests_mock): ) with nested_session(session): + auth_cache.clear() + clear_db(session) rv = client.post( '/plots', json=json_data, @@ -644,6 +702,8 @@ def test_post_plots_bor_error(app, client, session, jwt, requests_mock): ) with nested_session(session): + auth_cache.clear() + clear_db(session) rv = client.post( '/plots', json=json_data, @@ -700,6 +760,8 @@ def test_post_plots_email_error(app, client, session, jwt, requests_mock): ) with nested_session(session): + auth_cache.clear() + clear_db(session) rv = client.post( '/plots', json=json_data, @@ -770,6 +832,7 @@ def test_post_plots_invalid_entity( ) with nested_session(session): + clear_db(session) rv = client.post( '/plots', json=json_data, @@ -790,33 +853,55 @@ def test_post_plots_invalid_entity( assert email_mock.called == False -def test_get_latest_for_entity(app, client, session, jwt, requests_mock): - """Assure latest submission details are returned.""" - """However, there is redaction here based on jwt""" +def test_get_latest_for_entity(app, client, session, jwt, requests_mock, sample_user): + """Assure latest submission details are returned. However, there is redaction here based on jwt""" + requests_mock.post(app.config.get('SSO_SVC_TOKEN_URL'), json={'access_token': 'token'}) + requests_mock.post(f"{app.config.get('NOTIFY_SVC_URL')}", json={}) with nested_session(session): # Setup - user = UserModel() - user.save() + clear_db(session) + sample_user.save() test_identifier = 'id0' + requests_mock.get(f"{app.config.get('LEGAL_SVC_URL')}/businesses/{test_identifier}", + json=mocked_entity_response) + requests_mock.get( + f"{app.config.get('LEGAL_SVC_URL')}/businesses/{test_identifier}/addresses?addressType=deliveryAddress", + json=mocked_entity_address_response + ) s1_dict = { 'businessIdentifier': test_identifier, 'effectiveDate': '2020-01-13', - 'ownershipOrControlStatements': {'details': 's1'}, + 'ownershipOrControlStatements': [{ + 'details': 's1', + 'interestedParty': {'describedByPersonStatement': helper_people[0]['statementID']} + }], + 'personStatements': helper_people } + s1 = SubmissionService.create_submission(s1_dict, sample_user.id) + s1.save() s2_dict = { 'businessIdentifier': test_identifier, 'effectiveDate': '2021-01-13', - 'ownershipOrControlStatements': {'details': 's2'}, - 'personStatements': helper_people, + 'ownershipOrControlStatements': [{ + 'details': 's2', + 'interestedParty': {'describedByPersonStatement': s1.person_statements_json[0]['statementID']}, + 'statementID': s1.ownership_statements[0].ownership_json['statementID'] + }], + 'personStatements': s1.person_statements_json } s3_dict = { 'businessIdentifier': test_identifier + 's3', 'effectiveDate': '2024-04-22', - 'ownershipOrControlStatements': {'details': 's3'}, + 'ownershipOrControlStatements': [{ + 'details': 's3', + 'interestedParty': {'describedByPersonStatement': s1.person_statements_json[0]['statementID']}, + 'statementID': s1.ownership_statements[0].ownership_json['statementID'] + }], + 'personStatements': s1.person_statements_json } - (SubmissionService.create_submission(s1_dict, user.id)).save() - (SubmissionService.create_submission(s2_dict, user.id)).save() - (SubmissionService.create_submission(s3_dict, user.id)).save() + + (SubmissionService.create_submission(s2_dict, sample_user.id)).save() + (SubmissionService.create_submission(s3_dict, sample_user.id)).save() mock_account_id = 1 requests_mock.get( @@ -830,7 +915,7 @@ def test_get_latest_for_entity(app, client, session, jwt, requests_mock): ) # Test rv = client.get( - f"/plots/entity/{test_identifier}?account_id={mock_account_id}", + f'/plots/entity/{test_identifier}?account_id={mock_account_id}', headers=create_header( jwt_manager=jwt, roles=['basic'], @@ -845,21 +930,31 @@ def test_get_latest_for_entity(app, client, session, jwt, requests_mock): assert s2_dict['effectiveDate'] == rv.json['payload'].get('effectiveDate') -def test_get_redacted_for_entity(app, client, session, jwt, requests_mock): - """Assure latest submission details are returned.""" - """However, there is redaction here based on jwt""" +def test_get_redacted_for_entity(app, client, session, jwt, requests_mock, sample_user): + """Assure latest submission details are returned. However, there is redaction here based on jwt""" + requests_mock.post(app.config.get('SSO_SVC_TOKEN_URL'), json={'access_token': 'token'}) + requests_mock.post(f"{app.config.get('NOTIFY_SVC_URL')}", json={}) with nested_session(session): # Setup - user = UserModel() - user.save() + clear_db(session) + sample_user.save() test_identifier = 'id0' + requests_mock.get(f"{app.config.get('LEGAL_SVC_URL')}/businesses/{test_identifier}", + json=mocked_entity_response) + requests_mock.get( + f"{app.config.get('LEGAL_SVC_URL')}/businesses/{test_identifier}/addresses?addressType=deliveryAddress", + json=mocked_entity_address_response + ) s1_dict = { 'businessIdentifier': test_identifier, 'effectiveDate': '2021-01-13', - 'ownershipOrControlStatements': {'details': 's2'}, + 'ownershipOrControlStatements': [{ + 'details': 's2', + 'interestedParty': {'describedByPersonStatement': helper_people[0]['statementID']} + }], 'personStatements': helper_people, } - (SubmissionService.create_submission(s1_dict, user.id)).save() + (SubmissionService.create_submission(s1_dict, sample_user.id)).save() mock_account_id = 1 @@ -874,7 +969,7 @@ def test_get_redacted_for_entity(app, client, session, jwt, requests_mock): ) # Test rv = client.get( - f"/plots/entity/{test_identifier}?account_id={mock_account_id}", + f'/plots/entity/{test_identifier}?account_id={mock_account_id}', headers=create_header( jwt_manager=jwt, roles=['basic'], @@ -882,10 +977,19 @@ def test_get_redacted_for_entity(app, client, session, jwt, requests_mock): ), ) + # Confirm outcome + assert rv.status_code == HTTPStatus.OK + expected_dict = { 'businessIdentifier': 'id0', 'effectiveDate': '2021-01-13', - 'ownershipOrControlStatements': {'details': 's2'}, + 'ownershipOrControlStatements': [{ + 'details': 's2', + 'interestedParty': { + 'describedByPersonStatement': rv.json['payload']['personStatements'][0]['statementID'] + }, + 'statementID': rv.json['payload']['ownershipOrControlStatements'][0]['statementID'] + }], 'personStatements': [ { 'addresses': [ @@ -918,14 +1022,12 @@ def test_get_redacted_for_entity(app, client, session, jwt, requests_mock): 'street': ' ', 'streetAdditional': ' ', }, - 'uuid': '2c5fd9bc-2ff1-4545-86aa-d1c02705f4cd', + 'statementID': rv.json['payload']['personStatements'][0]['statementID'] } ], } - # Confirm outcome - assert rv.status_code == HTTPStatus.OK - + # validate assert expected_dict == rv.json.get('payload') assert test_identifier == rv.json['payload'].get('businessIdentifier') assert s1_dict['effectiveDate'] == rv.json['payload'].get('effectiveDate') diff --git a/btr-api/tests/unit/services/test_submission.py b/btr-api/tests/unit/services/test_submission.py index 908eafde..da265128 100644 --- a/btr-api/tests/unit/services/test_submission.py +++ b/btr-api/tests/unit/services/test_submission.py @@ -1,15 +1,38 @@ from datetime import date from btr_api.services.submission import SubmissionService + from tests.unit import nested_session +from tests.unit.models.test_user import sample_user from tests.unit.utils import SUBMISSION_DICT +from tests.unit.utils.db_helpers import clear_db -def test_create_submission(session): +def test_create_submission(session, app, requests_mock, sample_user): """Assure the create submission works as expected.""" - with nested_session(session): - submission = SubmissionService.create_submission(submission_dict=SUBMISSION_DICT, submitter_id=1) + requests_mock.post(app.config.get('SSO_SVC_TOKEN_URL'), json={'access_token': 'token'}) + requests_mock.post(f"{app.config.get('NOTIFY_SVC_URL')}", json={}) + business_identifier = SUBMISSION_DICT['businessIdentifier'] + requests_mock.get(f"{app.config.get('LEGAL_SVC_URL')}/businesses/{business_identifier}", + json={'business': {'identifier': business_identifier, 'legalName': 'test'}}) + requests_mock.get( + f"{app.config.get('LEGAL_SVC_URL')}/businesses/{business_identifier}/addresses?addressType=deliveryAddress", + json={'deliveryAddress': {'addressCity': 'Vancouver', 'addressCountry': 'Canada', 'streetAddress': 'Fake Street'}} + ) + requests_mock.get(f"{app.config.get('AUTH_SVC_URL')}/entities/{business_identifier}", + json={'contacts': [{'email': 'test', 'phone': '123'}]}) + with nested_session(session): + clear_db(session) + sample_user.save() + submission = SubmissionService.create_submission(submission_dict=SUBMISSION_DICT, + submitter_id=sample_user.id) + submission.save() # Assert the properties of the resulting SubmissionModel instance assert submission.effective_date == date.fromisoformat(SUBMISSION_DICT['effectiveDate']) - assert submission.payload == SUBMISSION_DICT + assert submission.business_identifier == SUBMISSION_DICT['businessIdentifier'] + assert submission.submitter == sample_user + assert submission.submitted_payload == SUBMISSION_DICT + # Assert ownership / person statements created + assert len(submission.ownership_statements) == len(SUBMISSION_DICT['ownershipOrControlStatements']) + assert len(submission.person_statements_json) == len(SUBMISSION_DICT['personStatements']) diff --git a/btr-api/tests/unit/utils/db_helpers.py b/btr-api/tests/unit/utils/db_helpers.py new file mode 100644 index 00000000..2d3a2fe2 --- /dev/null +++ b/btr-api/tests/unit/utils/db_helpers.py @@ -0,0 +1,48 @@ +# Copyright © 2024 Province of British Columbia +# +# Licensed under the BSD 3 Clause License, (the "License"); +# you may not use this file except in compliance with the License. +# The template for the license can be found here +# https://opensource.org/license/bsd-3-clause/ +# +# Redistribution and use in source and binary forms, +# with or without modification, are permitted provided that the +# following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# 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. +"""DB tests helper methods.""" +from sqlalchemy import text + +from btr_api.models import User + + +def clear_db(session): + """Clear db of all data.""" + session.execute(text('delete from ownership')) + session.execute(text('delete from ownership_history')) + session.execute(text('delete from person')) + session.execute(text('delete from person_history')) + session.execute(text('delete from submission')) + session.execute(text('delete from submission_history')) + session.execute(text('delete from users')) \ No newline at end of file diff --git a/btr-web/btr-main-app/cypress/e2e/pages/reviewConfirm.cy.ts b/btr-web/btr-main-app/cypress/e2e/pages/reviewConfirm.cy.ts index 1b4b65ed..f6d704a3 100644 --- a/btr-web/btr-main-app/cypress/e2e/pages/reviewConfirm.cy.ts +++ b/btr-web/btr-main-app/cypress/e2e/pages/reviewConfirm.cy.ts @@ -143,8 +143,9 @@ describe('pages -> Review and Confirm', () => { // click 'back' to go back to non review page cy.get('[data-cy=button-control-right-button]').eq(0).should('have.text', 'Back') cy.get('[data-cy=button-control-right-button]').eq(0).click() - - cy.get('[data-cy="noSignificantIndividualsExist-checkbox"]').click().should('be.checked') + cy.wait(2000) // needs to wait for page hydration or it will fail sometimes (no calls to wait for) + cy.get('[data-cy="noSignificantIndividualsExist-checkbox"]').click() + cy.get('[data-cy="noSignificantIndividualsExist-checkbox"]').should('be.checked') // click 'Review and Confirm' button to go back to review the summary cy.get('[data-cy="button-control-right-button"]').click() diff --git a/btr-web/btr-main-app/services/file-significant-individual.ts b/btr-web/btr-main-app/services/file-significant-individual.ts index 40bf0db7..b5b72ae1 100644 --- a/btr-web/btr-main-app/services/file-significant-individual.ts +++ b/btr-web/btr-main-app/services/file-significant-individual.ts @@ -110,12 +110,11 @@ const getPersonAndOwnershipAndControlStatements = (sif: SignificantIndividualFil source, statementDate: todayIsoDateString(), statementType: BodsStatementTypeE.PERSON_STATEMENT, - - statementID: UUIDv4() // todo: fixme we should update schema only if there are changes to the schema itself.... + statementID: siSchema.uuid } const oocs: BtrBodsOwnershipOrControlI = { - statementID: UUIDv4(), + statementID: siSchema.ownershipStatementId || UUIDv4(), interests: SiSchemaToBtrBodsConverters.getInterests(siSchema), interestedParty: { describedByPersonStatement: personStatement.statementID }, isComponent: false, diff --git a/btr-web/btr-main-app/tests/mocks/btrSubmissionExample.ts b/btr-web/btr-main-app/tests/mocks/btrSubmissionExample.ts index 927114f3..7cea960a 100644 --- a/btr-web/btr-main-app/tests/mocks/btrSubmissionExample.ts +++ b/btr-web/btr-main-app/tests/mocks/btrSubmissionExample.ts @@ -611,7 +611,8 @@ export const expectedSisOutput: SiSchemaType[] = [ startDate: '' } ], - uuid: undefined, + ownershipStatementId: 'ea7e5edb-8d2c-4970-b90a-2e3a237fd67b', + uuid: 'b0882814-34e2-42e9-b065-a550d94c9df1', ui: { newOrUpdatedFields: [] } @@ -686,7 +687,8 @@ export const expectedSisOutput: SiSchemaType[] = [ startDate: '' } ], - uuid: undefined, + ownershipStatementId: 'bee007d7-0520-4f2d-b6f0-a74a999d956f', + uuid: 'b04ce8de-cd95-4fa2-991d-3a06fe34deb0', ui: { newOrUpdatedFields: [] } @@ -758,7 +760,8 @@ export const expectedSisOutput: SiSchemaType[] = [ startDate: '2024-01-05' } ], - uuid: undefined, + ownershipStatementId: 'c41ede1d-4502-4711-aebd-833642e98ff7', + uuid: '0a9d4f95-ca72-42b4-8906-05d889e4bb52', ui: { newOrUpdatedFields: [] } diff --git a/btr-web/btr-main-app/utils/btr-bods/bods-to-si-schema-converters.ts b/btr-web/btr-main-app/utils/btr-bods/bods-to-si-schema-converters.ts index c9f5c14c..9a2f6f6d 100644 --- a/btr-web/btr-main-app/utils/btr-bods/bods-to-si-schema-converters.ts +++ b/btr-web/btr-main-app/utils/btr-bods/bods-to-si-schema-converters.ts @@ -219,7 +219,8 @@ const _getSi = ( effectiveDates: _getEffectiveDates(oocs), - uuid: person.uuid, + uuid: person.statementID, + ownershipStatementId: oocs.statementID, ui: { newOrUpdatedFields: [] diff --git a/btr-web/btr-main-app/utils/si-schema/definitions.ts b/btr-web/btr-main-app/utils/si-schema/definitions.ts index 28f04624..640ab873 100644 --- a/btr-web/btr-main-app/utils/si-schema/definitions.ts +++ b/btr-web/btr-main-app/utils/si-schema/definitions.ts @@ -85,6 +85,7 @@ export const SiSchema = z.object({ effectiveDates: z.array(StartEndDateGroup).min(1), uuid: z.string().min(1), + ownershipStatementId: z.string().optional(), // UI helper values ui: z.object({