Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""Add REVIEW_NOT_COMPLETED package status

Revision ID: db8937ea18af
Revises: 77862b346adf
Create Date: 2026-03-27 09:03:51.152279

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = 'db8937ea18af'
down_revision = '4eab27236b2a'
branch_labels = None
depends_on = None


def upgrade():
op.execute("ALTER TYPE packagestatus ADD VALUE IF NOT EXISTS 'REVIEW_NOT_COMPLETED';")
op.execute("ALTER TYPE itemstatus ADD VALUE IF NOT EXISTS 'REVIEW_NOT_COMPLETED';")


def downgrade():
pass
1 change: 1 addition & 0 deletions submit-api/src/submit_api/enums/item_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class ItemStatus(enum.Enum):
UNDER_REVIEW = 'UNDER_REVIEW'
UNDER_CONSULTATION_CHECK = 'UNDER_CONSULTATION_CHECK'
REVIEW_REJECTED = 'REVIEW_REJECTED'
REVIEW_NOT_COMPLETED = 'REVIEW_NOT_COMPLETED'
APPROVED = 'APPROVED'
ACCEPTED = 'ACCEPTED'
SATISFIED = 'SATISFIED'
Expand Down
1 change: 1 addition & 0 deletions submit-api/src/submit_api/models/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class PackageStatus(enum.Enum):
UNDER_REVIEW = 'UNDER_REVIEW'
UNDER_CONSULTATION_CHECK = 'UNDER_CONSULTATION_CHECK'
REVIEW_REJECTED = 'REVIEW_REJECTED'
REVIEW_NOT_COMPLETED = 'REVIEW_NOT_COMPLETED'
CREATED = 'CREATED'
AWAITING_MANAGER_APPROVAL = 'AWAITING_MANAGER_APPROVAL'
CC_AWAITING_MANAGER_APPROVAL = 'CC_AWAITING_MANAGER_APPROVAL'
Expand Down
7 changes: 7 additions & 0 deletions submit-api/src/submit_api/models/queries/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ def _add_review_rejected(cls, aggregated_statuses: set, statuses: list[str]):
if any(status == ItemStatus.REVIEW_REJECTED.value for status in statuses):
aggregated_statuses.add(PackageStatus.REVIEW_REJECTED.value)

@classmethod
def _add_review_not_completed(cls, aggregated_statuses: set, statuses: list[str]):
"""Find packages that have not been completed during review"""
if any(status == ItemStatus.REVIEW_NOT_COMPLETED.value for status in statuses):
aggregated_statuses.add(PackageStatus.REVIEW_NOT_COMPLETED.value)

@classmethod
def _add_under_review(cls, aggregated_statuses: set, statuses: list[str]):
"""Find packages that have been rejected during review"""
Expand Down Expand Up @@ -127,6 +133,7 @@ def aggregate_item_statuses(cls, items: list):
cls._add_submitted_status(aggregated_statuses, statuses)
cls._add_passed_consultation_check(aggregated_statuses, statuses)
cls._add_review_rejected(aggregated_statuses, statuses)
cls._add_review_not_completed(aggregated_statuses, statuses)
cls._add_under_review(aggregated_statuses, statuses)
cls._add_under_cc_check(aggregated_statuses, statuses)
cls.add_awaiting_manager_review(aggregated_statuses, statuses)
Expand Down
7 changes: 7 additions & 0 deletions submit-api/src/submit_api/models/track_work.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ def to_dict(self):
"title": self.title,
}

def is_in_specific_phase(self, phase_name: str, work_type_name: str):
"""Determines if work is in specific phase."""
return (
self.current_phase.name == phase_name and
self.current_phase.work_type_name == work_type_name
)

@classmethod
def find_by_project_id(cls, project_id: int):
"""Return works by project id."""
Expand Down
5 changes: 5 additions & 0 deletions submit-api/src/submit_api/resources/proponent/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from flask_restx import Namespace, Resource

from submit_api.auth import auth
from submit_api.enums.role import ProponentPermissionsEnum
from submit_api.resources.apihelper import Api as ApiHelper
from submit_api.schemas.package import PackageSchema, PostPackageRequestSchema, PostPackageState, \
CreateUpdateRequestNoteSchema
Expand Down Expand Up @@ -83,6 +84,10 @@ class PackageByAccountProject(Resource):
@cross_origin(origins=allowedorigins())
def post(account_project_id):
"""Create a submission package."""
authorization.check_has_permissions_on_project(
[ProponentPermissionsEnum.CREATE_PACKAGE.value],
[account_project_id]
)
create_package_data = PostPackageRequestSchema().load(API.payload)
created_package = PackageService.create_first_package(account_project_id, create_package_data)
return PackageSchema().dump(created_package), HTTPStatus.CREATED
Expand Down
21 changes: 19 additions & 2 deletions submit-api/src/submit_api/services/invitation_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,22 @@

from submit_api.enums.proponent_status import ProponentStatus
from submit_api.enums.role import ProponentPermissionsEnum
from submit_api.enums.work_type import WorkTypeName
from submit_api.exceptions import ResourceNotFoundError, BadRequestError
from submit_api.models import AccountProject as AccountProjectModel, User
from submit_api.models.account import Account as AccountModel
from submit_api.models.db import session_scope
from submit_api.models.email_queue import EmailQueue as EmailQueueModel
from submit_api.models.email_queue import EntityType
from submit_api.models.invitations import Invitations as InvitationsModel, InvitationStatus
from submit_api.models.package import PackageStatus
from submit_api.models.proponent import Proponent as ProponentModel
from submit_api.models.role import Role as RoleModel
from submit_api.models.account_terms_of_service import TermsOfService as TermsOfServiceModel
from submit_api.models.user import UserType
from submit_api.services import authorization
from submit_api.services.account_user_service import AccountUserService
from submit_api.services.package import PackageService
from submit_api.services.user_service import UserService
from submit_api.utils.constants import NEW_USER_INVITATION_EMAIL_TEMPLATE
from submit_api.utils.token_info import TokenInfo
Expand Down Expand Up @@ -179,7 +182,7 @@ def _create_email_queue_record(cls, invitation_id, session=None):
session.commit()

@staticmethod
def accept_invitation(token, payload):
def accept_invitation(token, payload): # pylint: disable=too-many-locals
"""Accept an invitation and assign access to an account."""
invitation = InvitationsModel.validate_token(token)
if not invitation:
Expand Down Expand Up @@ -215,8 +218,22 @@ def accept_invitation(token, payload):

# Create account_project_works
works = TrackWork.find_by_project_id(account_project.project_id)
account_project_works = []
for work in works:
AccountProjectWork.create_or_get(account_project.id, work.id)
account_project_work = AccountProjectWork.create_or_get(account_project.id, work.id)
account_project_works.append(account_project_work)

# Create default submission package (if required by EAO)
if any(
apw.work.is_in_specific_phase('Early Engagement', WorkTypeName.ASSESSMENT)
for apw in account_project_works
):
PackageService.create_first_package(account_project.id, {
"type": "IPD",
"name": "Initial Project Description & Engagement Plan",
"status": [PackageStatus.REQUESTED_BY_EAO.value],
"metadata": {}
})

InvitationsModel.mark_used(token, account_user.user_id, session)

Expand Down
13 changes: 9 additions & 4 deletions submit-api/src/submit_api/services/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,6 @@ def create_new_package_from_original(cls, current_package_id, session):
@classmethod
def create_first_package(cls, account_project_id, request_data):
"""Create a new package."""
authorization.check_has_permissions_on_project(
[ProponentPermissionsEnum.CREATE_PACKAGE.value],
[account_project_id]
)
with session_scope() as session:
package_type = PackageTypeModel.find_by_name(
request_data.get("type"))
Expand Down Expand Up @@ -86,6 +82,9 @@ def _create_package(session, account_project_id, request_data, package_type):
"name": request_data.get("name"),
"type_id": package_type.id,
}
if status := request_data.get("status"):
package_data["status"] = status

package = PackageModel(**package_data)
session.add(package)
session.flush()
Expand Down Expand Up @@ -119,6 +118,12 @@ def create_new_package_version_with_contacts(cls, package_id):
if user:
new_package.submitted_by = user.auth_guid

# Update item and package statuses
statuses_to_update = [ItemStatus.UNDER_REVIEW, ItemStatus.UNDER_CONSULTATION_CHECK]
items_to_update = [i for i in original_package.items if i.status in statuses_to_update]
cls._update_items_status(items_to_update, ItemStatus.REVIEW_NOT_COMPLETED.value, session)
cls._update_package_status(original_package.id, session, original_package)

ActivityLogService.log_activity(
entity_id=original_package.version.original_package_id,
action=ActivityActionType.RESUBMISSION_INVITATION.value,
Expand Down
10 changes: 10 additions & 0 deletions submit-web/src/components/App/PackageStatusChip/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@ const statusStyles: Record<
width: "125px",
},
},
REVIEW_NOT_COMPLETED: {
sx: {
borderRadius: 1,
border: `1px solid ${BCDesignTokens.supportBorderColorWarning}`,
background: BCDesignTokens.supportSurfaceColorWarning,
height: "24px",
width: "168px",
},
label: "Review Not Completed",
},
SUBMITTED: {
sx: {
borderRadius: 1,
Expand Down
6 changes: 6 additions & 0 deletions submit-web/src/components/App/Submission/VersionGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ export default function VersionGroup({
const { mutate: createNewPackageVersion } = useCreateNewPackageVersion({
onSuccess: (newPackage) => {
notify.success("New package created successfully");

// Invalidate the old package so it's refetched when revisited
queryClient.invalidateQueries({
queryKey: [QUERY_KEY.SUBMISSION_PACKAGE, packageId],
});

loadNewPackage(newPackage.id);
},
onError: () => {
Expand Down
10 changes: 10 additions & 0 deletions submit-web/src/components/App/SubmissionStatusChip/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ const statusStyles: Record<string, StyleProps> = {
},
label: "Under Review",
},
REVIEW_NOT_COMPLETED: {
sx: {
borderRadius: 1,
border: `1px solid ${BCDesignTokens.supportBorderColorWarning}`,
background: BCDesignTokens.supportSurfaceColorWarning,
height: "24px",
width: "168px",
},
label: "Review Not Completed",
},
UNDER_CONSULTATION_CHECK: {
sx: {
borderRadius: 1,
Expand Down
5 changes: 5 additions & 0 deletions submit-web/src/models/Package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export type PackageStatus =
| "ACCEPTED"
| "SATISFIED"
| "REVIEW_REJECTED"
| "REVIEW_NOT_COMPLETED"
| "REVIEWED"
| "COMPLETED"
| "SUBMITTED"
Expand Down Expand Up @@ -73,6 +74,10 @@ export const PACKAGE_STATUS: Record<
value: "REVIEW_REJECTED",
label: "Rejected",
},
REVIEW_NOT_COMPLETED: {
value: "REVIEW_NOT_COMPLETED",
label: "Review Not Completed",
},
COMPLETED: {
value: "COMPLETED",
label: "Completed",
Expand Down
7 changes: 6 additions & 1 deletion submit-web/src/models/Submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export type SubmissionItemStatus =
| "PARTIALLY_COMPLETED"
| "SUBMITTED"
| "REVIEW_REJECTED"
| "REVIEW_NOT_COMPLETED"
| "FAILED_CONSULTATION_CHECK"
| "PASSED_CONSULTATION_CHECK"
| "REVISION_REQUIRED"
Expand Down Expand Up @@ -76,9 +77,13 @@ export const SUBMISSION_ITEM_STATUS: Record<
value: "REVIEW_REJECTED",
label: "Review Rejected",
},
REVIEW_NOT_COMPLETED: {
value: "REVIEW_REJECTED",
label: "Review Rejected",
},
FAILED_CONSULTATION_CHECK: {
value: "FAILED_CONSULTATION_CHECK",
label: "Failed Consultation Check",
label: "Review Not Completed",
},
APPROVED: {
value: "APPROVED",
Expand Down
Loading