Skip to content

Commit

Permalink
(PC-32811)[API] feat: set update requests to without continuation eve…
Browse files Browse the repository at this point in the history
…ry 30d
  • Loading branch information
vroullier-pass committed Jan 22, 2025
1 parent e6f4aa1 commit 51cc01e
Show file tree
Hide file tree
Showing 12 changed files with 555 additions and 43 deletions.
2 changes: 1 addition & 1 deletion api/src/pcapi/connectors/dms/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
GET_APPLICATIONS_WITH_DETAILS_QUERY_NAME = "beneficiaries/get_applications_with_details"
MAKE_ON_GOING_MUTATION_NAME = "make_on_going"
MAKE_ACCEPTED_MUTATION_NAME = "make_accepted"
MARK_WITHOUT_CONTINUATION_MUTATION_NAME = "mark_wihtout_continuation"
MARK_WITHOUT_CONTINUATION_MUTATION_NAME = "mark_without_continuation"
SEND_USER_MESSAGE_QUERY_NAME = "send_user_message"
UPDATE_TEXT_ANNOTATION_QUERY_NAME = "update_text_annotation"
GET_EAC_APPLICATIONS_STATE_SIRET = "eac/get_applications_state_siret"
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
mutation markWithoutContinuation($input: DossierClasserSansSuiteInput!) {
dossierClasserSansSuite(input: $input) {
dossier {
id
number
state
dateDerniereModification
dateDepot
datePassageEnConstruction
datePassageEnInstruction
dateTraitement
dateDerniereCorrectionEnAttente
dateDerniereModificationChamps
}
errors {
message
}
}
}
5 changes: 3 additions & 2 deletions api/src/pcapi/connectors/dms/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@

def import_ds_applications(
procedure_number: int,
callback: typing.Callable[[int, datetime.datetime | None], list],
callback: typing.Callable[[int, datetime.datetime | None, bool], list],
ignore_previous: bool = False,
set_without_continuation: bool = False,
forced_since: datetime.datetime | None = None,
) -> None:
logger.info("[DS] Start import of all applications from Démarches Simplifiées for procedure %s", procedure_number)
Expand Down Expand Up @@ -53,7 +54,7 @@ def import_ds_applications(
db.session.add(current_import)
db.session.commit()

application_numbers = callback(procedure_number, since)
application_numbers = callback(procedure_number, since, set_without_continuation)

current_import.processedApplications = application_numbers
current_import.isProcessing = False
Expand Down
6 changes: 5 additions & 1 deletion api/src/pcapi/core/finance/ds.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@
MARK_WITHOUT_CONTINUATION_MOTIVATION = "Marked without continuation & archived through automatic process (PC-24035)"


def update_ds_applications_for_procedure(procedure_number: int, since: datetime.datetime | None) -> list:
def update_ds_applications_for_procedure(
procedure_number: int,
since: datetime.datetime | None,
set_without_continuation: bool = False,
) -> list:
logger.info("[DS] Started processing Bank Account procedure %s", procedure_number)

ds_client = ds_api.DMSGraphQLClient()
Expand Down
3 changes: 3 additions & 0 deletions api/src/pcapi/core/mails/transactional/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,6 @@
from .users.suspicious_login_email import send_suspicious_login_email
from .users.ubble.subscription_document_error import send_subscription_document_error_email
from .users.unsuspension import send_unsuspension_email
from .users.update_request_set_to_without_continuation import (
send_beneficiary_update_request_set_to_without_continuation,
)
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ class TransactionalEmail(Enum):
)
BENEFICIARY_PRE_ANONYMIZATION = models.Template(id_prod=1388, id_not_prod=166)
PERSONAL_DATA_UPDATED_FROM_BACKOFFICE = models.Template(id_prod=1393, id_not_prod=169, use_priority_queue=True)
UPDATE_REQUEST_MARKED_WITHOUT_CONTINUATION = models.Template(id_prod=1442, id_not_prod=174)

# UBBLE KO REMINDER
UBBLE_KO_REMINDER_ID_CHECK_DATA_MATCH = models.Template(id_prod=824, id_not_prod=116)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import datetime

from pcapi.core import mails
from pcapi.core.mails import models
from pcapi.core.mails.transactional.sendinblue_template_ids import TransactionalEmail
import pcapi.core.users.models as users_models
from pcapi.utils.date import get_date_formatted_for_email


def send_beneficiary_update_request_set_to_without_continuation(user: users_models.User) -> None:

data = models.TransactionalEmailData(
template=TransactionalEmail.UPDATE_REQUEST_MARKED_WITHOUT_CONTINUATION.value,
params={"DATE_FILECLASSIFIED_DMS": get_date_formatted_for_email(datetime.datetime.utcnow())},
)
mails.send(recipients=[user.email], data=data)
34 changes: 22 additions & 12 deletions api/src/pcapi/core/users/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import pcapi.core.chronicles.api as chronicles_api
from pcapi.core.mails.transactional.users import online_event_reminder
from pcapi.core.users import ds as users_ds
import pcapi.core.users.api as user_api
import pcapi.core.users.api as users_api
import pcapi.core.users.constants as users_constants
from pcapi.models import db
from pcapi.models.feature import FeatureToggle
Expand All @@ -22,7 +22,7 @@
@blueprint.cli.command("notify_users_before_deletion_of_suspended_account")
@cron_decorators.log_cron_with_transaction
def notify_users_before_deletion_of_suspended_account() -> None:
user_api.notify_users_before_deletion_of_suspended_account()
users_api.notify_users_before_deletion_of_suspended_account()


@blueprint.cli.command("notify_users_before_online_event")
Expand All @@ -34,9 +34,9 @@ def notify_users_before_online_event() -> None:
@blueprint.cli.command("delete_suspended_accounts_after_withdrawal_period")
@cron_decorators.log_cron_with_transaction
def delete_suspended_accounts_after_withdrawal_period() -> None:
query = user_api.get_suspended_upon_user_request_accounts_since(settings.DELETE_SUSPENDED_ACCOUNTS_SINCE)
query = users_api.get_suspended_upon_user_request_accounts_since(settings.DELETE_SUSPENDED_ACCOUNTS_SINCE)
for user in query:
user_api.suspend_account(user, reason=users_constants.SuspensionReason.DELETED, actor=None)
users_api.suspend_account(user, reason=users_constants.SuspensionReason.DELETED, actor=None)


@blueprint.cli.command("anonymize_inactive_users")
Expand All @@ -62,28 +62,28 @@ def delete_suspended_accounts_after_withdrawal_period() -> None:
def anonymize_inactive_users(category: str, force: bool) -> None:
if category in ("beneficiary", "all"):
print("Anonymize beneficiary users after 5 years")
user_api.anonymize_beneficiary_users(force=force)
user_api.anonymize_user_deposits()
users_api.anonymize_beneficiary_users(force=force)
users_api.anonymize_user_deposits()
chronicles_api.anonymize_unlinked_chronicles()
if category in ("neither", "all"):
print("Anonymizing users that are neither beneficiaries nor pro 3 years after their last connection")
user_api.anonymize_non_pro_non_beneficiary_users(force=force)
users_api.anonymize_non_pro_non_beneficiary_users(force=force)
if category in ("pro", "all"):
print("Anonymizing pro users X years after their last connection")
user_api.anonymize_pro_users()
users_api.anonymize_pro_users()


@blueprint.cli.command("execute_gdpr_extract")
@cron_decorators.log_cron_with_transaction
def execute_gdpr_extract() -> None:
user_api.extract_beneficiary_data_command()
users_api.extract_beneficiary_data_command()
db.session.commit()


@blueprint.cli.command("clean_gdpr_extracts")
@cron_decorators.log_cron_with_transaction
def clean_gdpr_extracts() -> None:
user_api.clean_gdpr_extracts()
users_api.clean_gdpr_extracts()
db.session.commit()


Expand All @@ -109,8 +109,15 @@ def sync_ds_instructor_ids() -> None:
default=False,
help="Import all application ignoring previous import date",
)
@click.option(
"--set-without-continuation",
type=bool,
is_flag=True,
default=False,
help="Set unanswered for 30 days applications to without continuation",
)
@cron_decorators.log_cron_with_transaction
def sync_ds_user_account_update_requests(ignore_previous: bool = False) -> None:
def sync_ds_user_account_update_requests(ignore_previous: bool = False, set_without_continuation: bool = False) -> None:
if not FeatureToggle.ENABLE_DS_SYNC_FOR_USER_ACCOUNT_UPDATE_REQUESTS.is_active():
return

Expand All @@ -119,7 +126,10 @@ def sync_ds_user_account_update_requests(ignore_previous: bool = False) -> None:
]
for procedure_id in procedure_ids:
import_ds_applications(
int(procedure_id), users_ds.sync_user_account_update_requests, ignore_previous=ignore_previous
int(procedure_id),
users_ds.sync_user_account_update_requests,
ignore_previous=ignore_previous,
set_without_continuation=set_without_continuation,
)
db.session.commit()

Expand Down
62 changes: 56 additions & 6 deletions api/src/pcapi/core/users/ds.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
import enum
import logging

from dateutil.relativedelta import relativedelta
import sqlalchemy as sa

from pcapi import settings
from pcapi.connectors.dms import api as ds_api
from pcapi.connectors.dms import models as dms_models
import pcapi.core.mails.transactional as transactional_mails
from pcapi.core.permissions import models as perm_models
from pcapi.core.subscription.phone_validation import exceptions as phone_validation_exceptions
from pcapi.core.users import models as users_models
Expand Down Expand Up @@ -73,7 +75,11 @@ def sync_instructor_ids(procedure_number: int) -> None:
db.session.flush()


def sync_user_account_update_requests(procedure_number: int, since: datetime.datetime | None) -> list:
def sync_user_account_update_requests(
procedure_number: int,
since: datetime.datetime | None,
set_without_continuation: bool = False,
) -> list:
logger.info("[DS] Started processing User Update Account procedure %s", procedure_number)

# Fetch instructors' User IDs only once
Expand All @@ -89,7 +95,7 @@ def sync_user_account_update_requests(procedure_number: int, since: datetime.dat

for node in ds_client.get_beneficiary_account_update_nodes(procedure_number=procedure_number, since=since):
try:
ds_application_id = _sync_ds_application(procedure_number, node, user_id_by_email)
ds_application_id = _sync_ds_application(procedure_number, node, user_id_by_email, set_without_continuation)
except Exception: # pylint: disable=broad-exception-caught
# If we don't rollback here, we will persist in the faulty transaction
# and we won't be able to commit at the end of the process and to set the current import `isProcessing` attr to False
Expand Down Expand Up @@ -139,7 +145,36 @@ def _get_updated_data(node: dict) -> dict:
}


def _sync_ds_application(procedure_number: int, node: dict, user_id_by_email: dict) -> int | None:
def check_set_without_continuation(user_request: users_models.UserAccountUpdateRequest, node: dict, data: dict) -> None:
if (
user_request.status
in (
dms_models.GraphQLApplicationStates.draft,
dms_models.GraphQLApplicationStates.on_going,
)
and data["dateLastInstructorMessage"] + relativedelta(days=30)
< datetime.datetime.utcnow().astimezone(datetime.timezone.utc)
and data["dateLastInstructorMessage"]
> max([_from_ds_date(node["dateDerniereModification"]), data["dateLastUserMessage"]])
):
if user_request.status == dms_models.GraphQLApplicationStates.draft:
update_state(
user_request,
new_state=dms_models.GraphQLApplicationStates.on_going,
instructor=user_request.lastInstructor,
)
update_state(
user_request,
new_state=dms_models.GraphQLApplicationStates.without_continuation,
instructor=user_request.lastInstructor,
motivation="Dossier classé sans suite car pas de correction apportée au dossier depuis 30 jours",
)
transactional_mails.send_beneficiary_update_request_set_to_without_continuation(user_request.user)


def _sync_ds_application(
procedure_number: int, node: dict, user_id_by_email: dict, set_without_continuation: bool
) -> int | None:
try:
ds_application_id = node["number"]
fields = node.get("champs", [])
Expand Down Expand Up @@ -250,6 +285,11 @@ def _sync_ds_application(procedure_number: int, node: dict, user_id_by_email: di
else:
user_request = users_models.UserAccountUpdateRequest(dsApplicationId=ds_application_id, **data)
db.session.add(user_request)
db.session.flush()

if set_without_continuation:
check_set_without_continuation(user_request, node, data)

except Exception as exc:
logger.exception(
"[DS] Application parsing failed with error %s",
Expand Down Expand Up @@ -292,20 +332,30 @@ def update_state(
user_request: users_models.UserAccountUpdateRequest,
*,
new_state: dms_models.GraphQLApplicationStates,
instructor: users_models.User,
instructor: users_models.User | None,
motivation: str | None = None,
) -> None:
ds_client = ds_api.DMSGraphQLClient()
if instructor is not None:
instructor_id = instructor.backoffice_profile.dsInstructorId
else:
instructor_id = settings.DMS_INSTRUCTOR_ID

if new_state == dms_models.GraphQLApplicationStates.on_going:
node = ds_client.make_on_going(
application_techid=user_request.dsTechnicalId,
instructeur_techid=instructor.backoffice_profile.dsInstructorId,
instructeur_techid=instructor_id,
)
elif new_state == dms_models.GraphQLApplicationStates.accepted:
node = ds_client.make_accepted(
application_techid=user_request.dsTechnicalId,
instructeur_techid=instructor.backoffice_profile.dsInstructorId,
instructeur_techid=instructor_id,
motivation=motivation,
)
elif new_state == dms_models.GraphQLApplicationStates.without_continuation:
node = ds_client.mark_without_continuation(
application_techid=user_request.dsTechnicalId,
instructeur_techid=instructor_id,
motivation=motivation,
)
else:
Expand Down
Loading

0 comments on commit 51cc01e

Please sign in to comment.