Skip to content

Commit 8ec1ec2

Browse files
authored
[Issue #6669] Checking and updating the data for ConfirmApplicationDelivery (#9064)
## Summary <!-- Use "Fixes" to automatically close issue upon PR merge. Use "Work for" when UAT is required. --> Fixes for #6669 ## Changes proposed - Created confirm_application_delivery_response.py to create the new service `confirm_application_delivery_response` - Updated __init__.py to add the new service - Updated legacy_soap_api_config.py to add `always_call_simpler=True` to `ConfirmApplicationDeliveryRequest` - Updated legacy_soap_client.py to add `confirm_application_delivery_request` to dispatch the new service - Created test_confirm_application_delivery_response.py to test new service - Updated competition_models.py to add `cascade="all, delete-orphan"` to `application_submission_retrievals` ## Context for reviewers Some notes on this: - Only `ACCEPTED` status is valid, `SUBMITTED` maps to "Received" in grants.gov, not "Validated" - The duplicate check is scoped per-user, meaning different users can each retrieve the same submission, but the same user calling twice gets the fault response - Schemas and operation config for `ConfirmApplicationDelivery` already existed, this PR adds the service logic ## Validation steps Confirm tests pass
1 parent 1ae1dcb commit 8ec1ec2

File tree

7 files changed

+514
-1
lines changed

7 files changed

+514
-1
lines changed

api/src/db/models/competition_models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,7 @@ class ApplicationSubmission(ApiSchemaTable, TimestampMixin):
467467
"ApplicationSubmissionRetrieved",
468468
back_populates="application_submission",
469469
uselist=True,
470+
cascade="all, delete-orphan",
470471
)
471472

472473
@property

api/src/legacy_soap_api/grantors/fault_messages.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,18 @@
99
faultstring="Failed to validate request.\ncvc-complex-type.2.4.b: The content of element UpdateApplicationInfoRequest is not complete. One of {https://apply.grants.gov/system/GrantsCommonElements-V1.0:GrantsGovTrackingNumber} is expected.",
1010
faultcode="soap:Server",
1111
)
12+
13+
ConfirmDeliverySubmissionNotFound = FaultMessage(
14+
faultstring="Unable to find application from tracking number. Failed to confirm application delivery.",
15+
faultcode="soap:Server",
16+
)
17+
18+
ConfirmDeliveryInvalidStatus = FaultMessage(
19+
faultstring="Invalid application status. Expected Accepted status. Failed to confirm application delivery.",
20+
faultcode="soap:Server",
21+
)
22+
23+
ConfirmDeliveryAlreadyRetrieved = FaultMessage(
24+
faultstring="This application submission has already been retrieved. Failed to confirm application delivery.",
25+
faultcode="soap:Server",
26+
)
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1+
from src.legacy_soap_api.grantors.services.confirm_application_delivery_response import (
2+
confirm_application_delivery_response,
3+
)
14
from src.legacy_soap_api.grantors.services.get_application_zip_response import (
25
get_application_zip_response,
36
)
47
from src.legacy_soap_api.grantors.services.get_submission_list_expanded_response import (
58
get_submission_list_expanded_response,
69
)
710

8-
__all__ = ["get_application_zip_response", "get_submission_list_expanded_response"]
11+
__all__ = [
12+
"confirm_application_delivery_response",
13+
"get_application_zip_response",
14+
"get_submission_list_expanded_response",
15+
]
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import logging
2+
3+
from apiflask.exceptions import HTTPError
4+
from sqlalchemy import select
5+
6+
import src.adapters.db as db
7+
from src.auth.endpoint_access_util import verify_access
8+
from src.constants.lookup_constants import ApplicationStatus
9+
from src.db.models.competition_models import ApplicationSubmission, ApplicationSubmissionRetrieved
10+
from src.legacy_soap_api.grantors import schemas as grantor_schemas
11+
from src.legacy_soap_api.grantors.fault_messages import (
12+
ConfirmDeliveryAlreadyRetrieved,
13+
ConfirmDeliveryInvalidStatus,
14+
ConfirmDeliverySubmissionNotFound,
15+
)
16+
from src.legacy_soap_api.legacy_soap_api_auth import (
17+
SOAPClientUserDoesNotHavePermission,
18+
validate_certificate,
19+
)
20+
from src.legacy_soap_api.legacy_soap_api_config import SOAPOperationConfig
21+
from src.legacy_soap_api.legacy_soap_api_constants import LegacySoapApiEvent
22+
from src.legacy_soap_api.legacy_soap_api_schemas import SOAPRequest
23+
from src.legacy_soap_api.legacy_soap_api_utils import SOAPFaultException
24+
25+
logger = logging.getLogger(__name__)
26+
27+
VALID_STATUSES_FOR_DELIVERY = {ApplicationStatus.ACCEPTED}
28+
29+
30+
def get_application_submission_by_legacy_tracking_number(
31+
db_session: db.Session, legacy_tracking_number: int
32+
) -> ApplicationSubmission | None:
33+
return db_session.execute(
34+
select(ApplicationSubmission).where(
35+
ApplicationSubmission.legacy_tracking_number == legacy_tracking_number,
36+
)
37+
).scalar_one_or_none()
38+
39+
40+
def confirm_application_delivery_response(
41+
db_session: db.Session,
42+
soap_request: SOAPRequest,
43+
confirm_application_delivery_request: grantor_schemas.ConfirmApplicationDeliveryRequest,
44+
soap_config: SOAPOperationConfig,
45+
) -> grantor_schemas.ConfirmApplicationDeliveryResponseSOAPEnvelope:
46+
legacy_tracking_number = confirm_application_delivery_request.grants_gov_tracking_number
47+
48+
# grants_gov_tracking_number is validated as non-None by the pydantic schema
49+
assert legacy_tracking_number is not None
50+
51+
if legacy_tracking_number.startswith("GRANT"):
52+
legacy_tracking_number = legacy_tracking_number.split("GRANT")[1]
53+
54+
application_submission = get_application_submission_by_legacy_tracking_number(
55+
db_session, int(legacy_tracking_number)
56+
)
57+
58+
if not application_submission:
59+
logger.info(
60+
f"Unable to find submission legacy_tracking_number {legacy_tracking_number}.",
61+
extra={
62+
"soap_api_event": LegacySoapApiEvent.ERROR_CALLING_SIMPLER,
63+
"response_operation_name": "ConfirmApplicationDeliveryResponse",
64+
},
65+
)
66+
raise SOAPFaultException(
67+
"Submission not found for confirm application delivery",
68+
fault=ConfirmDeliverySubmissionNotFound,
69+
)
70+
71+
application = application_submission.application
72+
73+
if application.application_status not in VALID_STATUSES_FOR_DELIVERY:
74+
logger.info(
75+
"Application status is not valid for confirm application delivery.",
76+
extra={
77+
"soap_api_event": LegacySoapApiEvent.ERROR_CALLING_SIMPLER,
78+
"application_status": application.application_status,
79+
"legacy_tracking_number": legacy_tracking_number,
80+
"response_operation_name": "ConfirmApplicationDeliveryResponse",
81+
},
82+
)
83+
raise SOAPFaultException(
84+
"Application status is not valid for confirm application delivery",
85+
fault=ConfirmDeliveryInvalidStatus,
86+
)
87+
88+
certificate = validate_certificate(
89+
db_session, soap_auth=soap_request.auth, api_name=soap_request.api_name
90+
)
91+
92+
if soap_config.privileges is None:
93+
raise ValueError("Privileges must be configured for ConfirmApplicationDelivery")
94+
95+
try:
96+
verify_access(
97+
certificate.user,
98+
soap_config.privileges,
99+
application.competition.opportunity.agency_record,
100+
)
101+
except HTTPError as e:
102+
logger.info(
103+
"User did not have permission to confirm application delivery",
104+
extra={
105+
"user_id": certificate.user.user_id,
106+
"application_submission_id": application_submission.application_submission_id,
107+
"privileges": soap_config.privileges,
108+
},
109+
)
110+
raise SOAPClientUserDoesNotHavePermission(
111+
"User did not have permission to confirm application delivery"
112+
) from e
113+
114+
# Check if this user has already retrieved this submission.
115+
existing_retrieval = (
116+
db_session.execute(
117+
select(ApplicationSubmissionRetrieved).where(
118+
ApplicationSubmissionRetrieved.application_submission_id
119+
== application_submission.application_submission_id,
120+
ApplicationSubmissionRetrieved.created_by_user_id == certificate.user.user_id,
121+
)
122+
)
123+
.scalars()
124+
.first()
125+
)
126+
127+
if existing_retrieval:
128+
logger.info(
129+
"Application submission has already been retrieved by this user.",
130+
extra={
131+
"soap_api_event": LegacySoapApiEvent.ERROR_CALLING_SIMPLER,
132+
"user_id": certificate.user.user_id,
133+
"legacy_tracking_number": legacy_tracking_number,
134+
"response_operation_name": "ConfirmApplicationDeliveryResponse",
135+
},
136+
)
137+
raise SOAPFaultException(
138+
"Application submission has already been retrieved by this user",
139+
fault=ConfirmDeliveryAlreadyRetrieved,
140+
)
141+
142+
retrieval = ApplicationSubmissionRetrieved(
143+
application_submission_id=application_submission.application_submission_id,
144+
created_by_user_id=certificate.user.user_id,
145+
modified_by_user_id=certificate.user.user_id,
146+
)
147+
db_session.add(retrieval)
148+
149+
return grantor_schemas.ConfirmApplicationDeliveryResponseSOAPEnvelope(
150+
grants_gov_tracking_number=confirm_application_delivery_request.grants_gov_tracking_number,
151+
response_message="Success",
152+
)

api/src/legacy_soap_api/legacy_soap_api_client.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from src.legacy_soap_api.applicants.services import get_opportunity_list_response
1111
from src.legacy_soap_api.grantors import schemas as grantors_schemas
1212
from src.legacy_soap_api.grantors.services import (
13+
confirm_application_delivery_response,
1314
get_application_zip_response,
1415
get_submission_list_expanded_response,
1516
)
@@ -242,6 +243,18 @@ def get_application_zip_request(
242243
soap_config=self.operation_config,
243244
)
244245

246+
def confirm_application_delivery_request(
247+
self, proxy_response: SOAPResponse | None = None
248+
) -> grantors_schemas.ConfirmApplicationDeliveryResponseSOAPEnvelope:
249+
return confirm_application_delivery_response(
250+
db_session=self.db_session,
251+
soap_request=self.soap_request,
252+
confirm_application_delivery_request=grantors_schemas.ConfirmApplicationDeliveryRequest(
253+
**self.get_soap_request_dict()
254+
),
255+
soap_config=self.operation_config,
256+
)
257+
245258
def get_submission_list_expanded_request(
246259
self, proxy_response: SOAPResponse
247260
) -> grantors_schemas.GetSubmissionListExpandedResponse:

api/src/legacy_soap_api/legacy_soap_api_config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ class SOAPOperationConfig:
152152
request_operation_name="ConfirmApplicationDeliveryRequest",
153153
response_operation_name="ConfirmApplicationDeliveryResponse",
154154
privileges={Privilege.LEGACY_AGENCY_GRANT_RETRIEVER},
155+
always_call_simpler=True,
155156
),
156157
"UpdateApplicationInfoRequest": SOAPOperationConfig(
157158
request_operation_name="UpdateApplicationInfoRequest",

0 commit comments

Comments
 (0)