Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

533 filing status prototype #540

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""Add filing state

Revision ID: 7fe49d38726b
Revises: 6ec12afa5b37
Create Date: 2024-12-30 13:05:01.303998

"""

from typing import Sequence, Union

from alembic import op, context
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision: str = "7fe49d38726b"
down_revision: Union[str, None] = "6ec12afa5b37"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None

filing_state_enum = postgresql.ENUM(
"OPEN",
"CLOSED",
name="filingstate",
create_type=False,
)


def upgrade() -> None:
filing_state_enum.create(op.get_bind(), checkfirst=True)
op.add_column(
"filing",
sa.Column("state", filing_state_enum),
)

op.create_table(
"filing_reopen",
sa.Column("user_action", sa.INTEGER, primary_key=True, unique=True, nullable=False),
sa.Column("filing", sa.Integer, nullable=False),
sa.PrimaryKeyConstraint("user_action", name="filing_reopen_pkey"),
sa.ForeignKeyConstraint(["user_action"], ["user_action.id"], name="filing_reopen_user_action_fkey"),
sa.ForeignKeyConstraint(["filing"], ["filing.id"], name="filing_reopen_filing_fkey"),
)


def downgrade() -> None:
op.drop_column("filing", "state")
op.drop_table("filing_reopen")

if "sqlite" not in context.get_context().dialect.name:
op.execute(sa.DDL("DROP TYPE filingstate"))
58 changes: 58 additions & 0 deletions db_revisions/versions/a655265a6c59_add_reopen_to_user_action.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""Add REOPEN to user action

Revision ID: a655265a6c59
Revises: 7fe49d38726b
Create Date: 2025-01-08 13:59:33.098890

"""

from typing import Sequence, Union

from alembic import op, context
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision: str = "a655265a6c59"
down_revision: Union[str, None] = "7fe49d38726b"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


old_user_action = postgresql.ENUM(
"SUBMIT",
"ACCEPT",
"SIGN",
"CREATE",
name="useractiontype",
create_type=False,
)

new_user_action = postgresql.ENUM(
"SUBMIT",
"ACCEPT",
"SIGN",
"CREATE",
"REOPEN",
name="useractiontype",
create_type=False,
)


def upgrade() -> None:
if "sqlite" not in context.get_context().dialect.name:
op.execute("ALTER TYPE useractiontype RENAME TO useractiontype_old")
new_user_action.create(op.get_bind(), checkfirst=True)
op.execute(
"ALTER TABLE user_action ALTER COLUMN action_type TYPE useractiontype USING user_action::text::useractiontype"
)
op.execute("DROP TYPE useractiontype_old")


def downgrade() -> None:
if "sqlite" not in context.get_context().dialect.name:
op.execute("ALTER TYPE useractiontype RENAME TO useractiontype_old")
old_user_action.create(op.get_bind(), checkfirst=True)
op.execute(
"ALTER TABLE user_action ALTER COLUMN action_type TYPE useractiontype USING user_action::text::useractiontype"
)
op.execute("DROP TYPE useractiontype_old")
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,9 @@ env = [
"FS_DOWNLOAD_CONFIG__PROTOCOL=file",
"ENV=TEST",
"MAIL_API_URL=http://mail-api:8765/internal/confirmation/send",
'REQUEST_VALIDATORS__SIGN_AND_SUBMIT=["valid_lei_status","valid_lei_tin","valid_filing_exists","valid_sub_accepted","valid_voluntary_filer","valid_contact_info"]',
'REQUEST_VALIDATORS__FILING_CREATE=["valid_period_exists", "valid_no_filing_exists"]'
'REQUEST_VALIDATORS__SIGN_AND_SUBMIT=["valid_lei_status","valid_lei_tin","valid_filing_exists_sign","valid_filing_open","valid_sub_accepted","valid_voluntary_filer","valid_contact_info"]',
'REQUEST_VALIDATORS__FILING_CREATE=["valid_period_exists", "valid_no_filing_exists"]',
'REQUEST_VALIDATORS__FILING_REOPEN=["valid_filing_exists_reopen", "valid_filing_not_open"]'
]
testpaths = ["tests"]

Expand Down
6 changes: 4 additions & 2 deletions src/.env.local
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,7 @@ FS_UPLOAD_CONFIG__ROOT="../upload"
EXPIRED_SUBMISSION_CHECK_SECS=120
SERVER_CONFIG__RELOAD="true"
MAIL_API_URL=http://mail-api:8765/internal/confirmation/send
REQUEST_VALIDATORS__SIGN_AND_SUBMIT=["valid_lei_status","valid_lei_tin","valid_filing_exists","valid_sub_accepted","valid_voluntary_filer","valid_contact_info"]
REQUEST_VALIDATORS__FILING_CREATE=["valid_period_exists", "valid_no_filing_exists"]
REQUEST_VALIDATORS__SIGN_AND_SUBMIT=["valid_lei_status","valid_lei_tin","valid_filing_exists_sign","valid_filing_open","valid_sub_accepted","valid_voluntary_filer","valid_contact_info"]
REQUEST_VALIDATORS__FILING_CREATE=["valid_period_exists", "valid_no_filing_exists"]
REQUEST_VALIDATORS__FILING_REOPEN=["valid_filing_exists_reopen", "valid_filing_not_open"]
USER_FI_API_URL=http://localhost:8881/v1/institutions/
5 changes: 4 additions & 1 deletion src/sbl_filing_api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,17 @@ class RequestActionValidations(BaseSettings):
sign_and_submit: Set[str] = {
"valid_lei_status",
"valid_lei_tin",
"valid_filing_exists",
"valid_filing_exists_sign",
"valid_filing_open",
"valid_sub_accepted",
"valid_voluntary_filer",
"valid_contact_info",
}

filing_create: Set[str] = {"valid_period_exists", "valid_no_filing_exists"}

filing_reopen: Set[str] = {"valid_filing_exists_reopen", "valid_filing_not_open"}

model_config = SettingsConfigDict(env_prefix="request_validators__", env_file=env_files_to_load, extra="allow")


Expand Down
20 changes: 19 additions & 1 deletion src/sbl_filing_api/entities/models/dao.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
from sbl_filing_api.entities.models.model_enums import FilingType, FilingTaskState, SubmissionState, UserActionType
from sbl_filing_api.entities.models.model_enums import (
FilingType,
FilingTaskState,
SubmissionState,
UserActionType,
FilingState,
)
from datetime import datetime
from typing import Any, List
from sqlalchemy import Enum as SAEnum, String, desc
Expand Down Expand Up @@ -107,6 +113,14 @@ class FilingSignatureDAO(Base):
filing: Mapped[int] = mapped_column(ForeignKey("filing.id"), index=True, nullable=False)


class FilingReopenDAO(Base):
__tablename__ = "filing_reopen"
user_action: Mapped[int] = mapped_column(
ForeignKey("user_action.id"), nullable=False, primary_key=True, unique=True
)
filing: Mapped[int] = mapped_column(ForeignKey("filing.id"), index=True, nullable=False)


class FilingDAO(Base):
__tablename__ = "filing"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
Expand All @@ -125,6 +139,10 @@ class FilingDAO(Base):
creator_id: Mapped[int] = mapped_column(ForeignKey("user_action.id"))
creator: Mapped[UserActionDAO] = relationship(lazy="selectin", foreign_keys=[creator_id])
is_voluntary: Mapped[bool] = mapped_column(nullable=True)
state: Mapped[FilingState | None] = mapped_column(nullable=True)
reopens: Mapped[List[UserActionDAO] | None] = relationship(
"UserActionDAO", secondary="filing_reopen", lazy="selectin", order_by="desc(UserActionDAO.timestamp)"
)

def __str__(self):
return f"ID: {self.id}, Filing Period: {self.filing_period}, LEI: {self.lei}, Tasks: {self.tasks}, Institution Snapshot ID: {self.institution_snapshot_id}, Contact Info: {self.contact_info}"
Expand Down
10 changes: 9 additions & 1 deletion src/sbl_filing_api/entities/models/dto.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@
from datetime import datetime
from typing import Dict, Any, List
from pydantic import BaseModel, ConfigDict, Field, model_validator
from sbl_filing_api.entities.models.model_enums import FilingType, FilingTaskState, SubmissionState, UserActionType
from sbl_filing_api.entities.models.model_enums import (
FilingType,
FilingTaskState,
SubmissionState,
UserActionType,
FilingState,
)


class UserActionDTO(BaseModel):
Expand Down Expand Up @@ -89,6 +95,8 @@ class FilingDTO(BaseModel):
signatures: List[UserActionDTO] = []
creator: UserActionDTO
is_voluntary: bool | None = None
state: FilingState | None = None
reopens: List[UserActionDTO] = []


class FilingPeriodDTO(BaseModel):
Expand Down
6 changes: 6 additions & 0 deletions src/sbl_filing_api/entities/models/model_enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class UserActionType(str, Enum):
ACCEPT = "ACCEPT"
SIGN = "SIGN"
CREATE = "CREATE"
REOPEN = "REOPEN"


class SubmissionState(str, Enum):
Expand All @@ -30,3 +31,8 @@ class FilingTaskState(str, Enum):

class FilingType(str, Enum):
ANNUAL = "ANNUAL"


class FilingState(str, Enum):
OPEN = "OPEN"
CLOSED = "CLOSED"
4 changes: 2 additions & 2 deletions src/sbl_filing_api/entities/repos/submission_repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
UserActionDAO,
)
from sbl_filing_api.entities.models.dto import FilingPeriodDTO, FilingDTO, ContactInfoDTO, UserActionDTO
from sbl_filing_api.entities.models.model_enums import SubmissionState
from sbl_filing_api.entities.models.model_enums import SubmissionState, FilingState

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -139,7 +139,7 @@ async def upsert_filing(session: AsyncSession, filing: FilingDTO) -> FilingDAO:


async def create_new_filing(session: AsyncSession, lei: str, filing_period: str, creator_id: int) -> FilingDAO:
new_filing = FilingDAO(filing_period=filing_period, lei=lei, creator_id=creator_id)
new_filing = FilingDAO(filing_period=filing_period, lei=lei, creator_id=creator_id, state=FilingState.OPEN)
return await upsert_helper(session, new_filing, FilingDAO)


Expand Down
29 changes: 28 additions & 1 deletion src/sbl_filing_api/routers/filing.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from regtech_api_commons.models.auth import AuthenticatedUser

from sbl_filing_api.entities.models.dao import FilingDAO
from sbl_filing_api.entities.models.model_enums import UserActionType
from sbl_filing_api.entities.models.model_enums import UserActionType, FilingState
from sbl_filing_api.services import submission_processor
from sbl_filing_api.services.multithread_handler import handle_submission
from sbl_filing_api.config import request_action_validations
Expand Down Expand Up @@ -139,6 +139,7 @@ async def sign_filing(request: Request, lei: str, period_code: str):
sig_timestamp = int(sig.timestamp.timestamp())
filing.confirmation_id = lei + "-" + period_code + "-" + str(latest_sub.counter) + "-" + str(sig_timestamp)
filing.signatures.append(sig)
filing.state = FilingState.CLOSED
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if we should add a check to ensure the Filing is in an OPEN state on signing. @lchen-2101 ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we prevent signing altogether if the state is not set to open?
And add a validator check to look at the state before proceeding?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we prevent signing altogether if the state is not set to open? And add a validator check to look at the state before proceeding?

That's what I am thinking, yeah.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This validation has been added.

send_confirmation_email(
request.user.name, request.user.email, filing.contact_info.email, filing.confirmation_id, sig_timestamp
)
Expand Down Expand Up @@ -411,3 +412,29 @@ async def update_is_voluntary(request: Request, lei: str, period_code: str, upda
name="Filing Not Found",
detail=f"A Filing for the LEI ({lei}) and period ({period_code}) that was attempted to be updated does not exist.",
)


@router.put(
"/institutions/{lei}/filings/{period_code}/reopen",
response_model=FilingDTO,
dependencies=[
Depends(set_context({UserActionContext.FILING})),
Depends(validate_user_action(request_action_validations.filing_reopen, "Filing Reopen Forbidden")),
],
)
@requires("authenticated")
async def reopen_filing(request: Request, lei: str, period_code: str):
reopen = await repo.add_user_action(
request.state.db_session,
UserActionDTO(
user_id=request.user.id,
user_name=request.user.name,
user_email=request.user.email,
action_type=UserActionType.REOPEN,
),
)
filing = await repo.get_filing(request.state.db_session, lei, period_code)
filing.reopens.append(reopen)
filing.state = FilingState.OPEN
res = await repo.upsert_filing(request.state.db_session, filing)
return res
32 changes: 30 additions & 2 deletions src/sbl_filing_api/services/validators/filing_validators.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging

from sbl_filing_api.entities.models.dao import FilingDAO
from sbl_filing_api.entities.models.model_enums import FilingState
from .base_validator import ActionValidator

log = logging.getLogger(__name__)
Expand All @@ -15,15 +16,24 @@ def __call__(self, filing: FilingDAO, period_code: str, lei: str, **kwargs):
return f"Filing already exists for Filing Period {period_code} and LEI {lei}"


class ValidFilingExists(ActionValidator):
class ValidFilingExistsSign(ActionValidator):
def __init__(self):
super().__init__("valid_filing_exists")
super().__init__("valid_filing_exists_sign")

def __call__(self, filing: FilingDAO, lei: str, period_code: str, **kwargs):
if not filing:
return f"There is no Filing for LEI {lei} in period {period_code}, unable to sign a non-existent Filing."


class ValidFilingExistsReopen(ActionValidator):
def __init__(self):
super().__init__("valid_filing_exists_reopen")

def __call__(self, filing: FilingDAO, lei: str, period_code: str, **kwargs):
if not filing:
return f"There is no Filing for LEI {lei} in period {period_code}, unable to reopen a non-existent Filing."


class ValidVoluntaryFiler(ActionValidator):
def __init__(self):
super().__init__("valid_voluntary_filer")
Expand All @@ -40,3 +50,21 @@ def __init__(self):
def __call__(self, filing: FilingDAO, **kwargs):
if filing and not filing.contact_info:
return f"Cannot sign filing. Filing for {filing.lei} for period {filing.filing_period} does not have contact info defined."


class ValidFilingOpen(ActionValidator):
def __init__(self):
super().__init__("valid_filing_open")

def __call__(self, *args, filing: FilingDAO, **kwargs):
if filing and filing.state is FilingState.CLOSED:
return f"Cannot sign filing. Filing state for {filing.lei} for period {filing.filing_period} is CLOSED."


class ValidFilingNotOpen(ActionValidator):
def __init__(self):
super().__init__("valid_filing_not_open")

def __call__(self, *args, filing: FilingDAO, **kwargs):
if filing and filing.state is FilingState.OPEN:
return f"Cannot reopen filing. Filing state for {filing.lei} for period {filing.filing_period} is OPEN."
Loading
Loading