Skip to content

Commit

Permalink
TRELO-2862 last chance pro reminder
Browse files Browse the repository at this point in the history
  • Loading branch information
ssedoudbgouv committed Jan 16, 2025
1 parent aa97e9b commit 51e77da
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 9 deletions.
27 changes: 27 additions & 0 deletions app/services/emails/EmailDefinitionsPro.scala
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,33 @@ object EmailDefinitionsPro {

}

case object ProReportsLastChanceReminder extends EmailDefinition {
override val category = Pro

override def examples = {
val report1 = genReport
val report2 = genReport.copy(companyId = report1.companyId)
val report3 = genReport.copy(companyId = report1.companyId, expirationDate = OffsetDateTime.now().plusDays(5))
Seq(
"reports_last_chance_reminder" -> ((recipient, _) =>
Email(List(recipient), List(report1, report2, report3), Period.ofDays(7))
)
)
}

final case class Email(
recipients: List[EmailAddress],
reports: List[Report],
period: Period
) extends ProFilteredEmailMultipleReport {
override val subject: String = EmailSubjects.REPORT_LAST_CHANCE_REMINDER

override def getBody: (FrontRoute, EmailAddress) => String =
(frontRoute, _) =>
views.html.mails.professional.reportsLastChanceReminder(reports, period)(frontRoute).toString
}
}

case object ProReportsReadReminder extends EmailDefinition {
override val category = Pro

Expand Down
22 changes: 16 additions & 6 deletions app/tasks/report/ReportRemindersTask.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import orchestrators.CompaniesVisibilityOrchestrator
import repositories.event.EventRepositoryInterface
import repositories.report.ReportRepositoryInterface
import repositories.tasklock.TaskRepositoryInterface
import services.emails.EmailDefinitionsPro.ProReportsReadReminder
import services.emails.EmailDefinitionsPro.ProReportsUnreadReminder
import services.emails.EmailDefinitionsPro.{ProReportsLastChanceReminder, ProReportsReadReminder, ProReportsUnreadReminder}
import services.emails.BaseEmail
import services.emails.MailServiceInterface
import tasks.ScheduledTask
Expand Down Expand Up @@ -50,6 +49,7 @@ class ReportRemindersTask(
// At J+0, the pro receives the "new report" email
// At J+8 during the night, the pro receives a reminder email
// At J+16 during the night, the pro receives the second reminder email
// At J+24 last reminder
// At J+25 the report is closed
val delayBetweenReminderEmails: Period = Period.ofDays(7)
val maxReminderCount = 2
Expand All @@ -74,11 +74,12 @@ class ReportRemindersTask(
shouldSendReminderEmail(report, taskRunDate, eventsByReportId)
}
_ = logger.info(s"Found ${finalReportsWithUsers.size} reports for which we should send a reminder")
result <- sendReminderEmailsWithErrorHandling(finalReportsWithUsers)
result <- sendReminderEmailsWithErrorHandling(taskRunDate, finalReportsWithUsers)
} yield result
}

private def sendReminderEmailsWithErrorHandling(
taskRunDate: OffsetDateTime,
reportsWithUsers: List[(Report, List[User])]
): Future[(List[List[UUID]], List[List[UUID]])] = {
logger.info(s"Sending reminders for ${reportsWithUsers.length} reports")
Expand All @@ -89,9 +90,17 @@ class ReportRemindersTask(
successesOrFailuresList <- Future.sequence(reportsPerCompanyPerUsers.toList.flatMap {
case (users, reportsPerCompany) =>
reportsPerCompany.map { reports =>
val (readByPros, notReadByPros) = reports.partition(_.isReadByPro)
val (reportsClosingTomorrow, otherReports) =
reports.partition(r => taskRunDate.toLocalDate.plusDays(1).isAfter(r.expirationDate.toLocalDate))
val (readByPros, notReadByPros) = otherReports.partition(_.isReadByPro)

for {
reportsClosingTomorrowSent <- sendReminderEmailIfAtLeastOneReport(
reportsClosingTomorrow,
users,
ProReportsLastChanceReminder.Email,
EMAIL_LAST_CHANCE_REMINDER_ACTION
)
readByProsSent <- sendReminderEmailIfAtLeastOneReport(
readByPros,
users,
Expand All @@ -104,7 +113,7 @@ class ReportRemindersTask(
ProReportsUnreadReminder.Email,
EMAIL_PRO_REMIND_NO_READING
)
} yield List(readByProsSent, notReadByProsSent).flatten
} yield List(readByProsSent, notReadByProsSent,reportsClosingTomorrowSent).flatten
}
})
(failures, successes) = successesOrFailuresList.flatten.partitionMap(identity)
Expand Down Expand Up @@ -147,7 +156,8 @@ class ReportRemindersTask(
previousEmailsEvents.count(e => reminderEmailsActions.contains(e.action)) >= maxReminderCount
val hadARecentEmail =
previousEmailsEvents.exists(_.creationDate.isAfter(taskRunDate.minus(delayBetweenReminderEmails)))
val shouldSendEmail = !hadMaxReminderEmails && !hadARecentEmail
val isLastDay = taskRunDate.toLocalDate.plusDays(1).isAfter(report.expirationDate.toLocalDate)
val shouldSendEmail = (!hadMaxReminderEmails && !hadARecentEmail) || isLastDay
shouldSendEmail
}

Expand Down
2 changes: 2 additions & 0 deletions app/utils/Constants.scala
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ object Constants {
extends ActionEventValue("Email « Nouveau signalement non consulté » envoyé au professionnel")
object EMAIL_PRO_REMIND_NO_ACTION
extends ActionEventValue("Email « Nouveau signalement en attente de réponse » envoyé au professionnel")
object EMAIL_LAST_CHANCE_REMINDER_ACTION
extends ActionEventValue("EmailLastChanceProReminder")

object REPORT_COMPANY_CHANGE extends ActionEventValue("Modification du commerçant")
object REPORT_COUNTRY_CHANGE extends ActionEventValue("Modification du pays")
Expand Down
1 change: 1 addition & 0 deletions app/utils/EmailSubjects.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ object EmailSubjects {
val REPORT_ASSIGNED = "Signalement affecté"
val REPORT_UNREAD_REMINDER = "Nouveau(x) signalement(s)"
val REPORT_TRANSMITTED_REMINDER = "Signalement(s) en attente de réponse"
val REPORT_LAST_CHANCE_REMINDER = "Signalement(s) expirant demain"
val REPORT_NOTIF_DGCCRF = (count: Int, additional: String) =>
s"[SignalConso] $additional${if (count > 1) s"$count nouveaux signalements ont été déposés"
else "Un nouveau signalement a été déposé"}"
Expand Down
2 changes: 1 addition & 1 deletion app/views/mails/professional/reportReOpening.scala.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<p>
Pour y apporter une réponse, il vous suffit de vous connecter sur votre <a href="@frontRoute.dashboard.url(s"/suivi-des-signalements/report/${report.id.toString}")">
Espace Professionnel</a> à l'aide de votre adresse e-mail et du mot de passe que vous avez créé à votre première connexion sur SignalConso.
Allez dans le détail du signalement et cliquez sur le bouton "Répondre à ce signalement".
Allez dans le détail du signalement et cliquez sur le bouton "Répondre".
</p>

<p>
Expand Down
71 changes: 71 additions & 0 deletions app/views/mails/professional/reportsLastChanceReminder.scala.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
@import utils.FrontRoute
@import models.report.Report
@import java.time.Period
@import java.time.OffsetDateTime
@import java.time.Duration
@(reports: List[Report], delay: Period)(implicit frontRoute: FrontRoute)

@views.html.mails.layout(s"Expiration de signalement(s).") {
<p>
Bonjour,
</p>

<p>
Vous avez @reports.length @plural(reports, "nouveau signalement", "nouveaux signalements") en attente depuis plus de @delay.getDays jours concernant votre entreprise :
</p>

<p style="text-align: center;">
@views.html.fragments.addressFromReport(reports.head, includeSiret = true)
</p>

<p>
@plural(reports, "Ce signalement expire", "Ces signalements expirent") demain. Passé ce délai, vous pourrez toujours @plural(reports, "le", "les") lire mais vous ne pourrez plus y répondre.
</p>

<p>
Pour @plural(reports, "le", "les") consulter, il vous suffit de vous connecter sur votre <a href="@frontRoute.dashboard.login">Espace Professionnel</a>
à l'aide de votre adresse e-mail et du mot de passe que vous avez créé à votre première connexion sur SignalConso.
</p>

<p>
<em>Si vous avez oublié votre mot de passe, vous pouvez le réinitialiser directement depuis la page de connexion.</em>
</p>

<p>
Pour apporter une réponse, allez sur votre Espace Professionnel. Dans le détail de chaque signalement, cliquez sur le bouton “Répondre”.
</p>

<p>
Vous pouvez également indiquer si vous estimez qu'un signalement est infondé ou ne concerne pas votre entreprise.
</p>

<p>
Ce service public est facultatif et gratuit.
À travers SignalConso, notre objectif est d’établir un rapport de confiance et de
transparence entre les consommateurs, les professionnels et les services de la DGCCRF.
</p>

<p>Cordialement,</p>

<p>
<i>L'équipe SignalConso</i>
</p>

@views.html.fragments.linkNotificationsManager()
}

@plural(reports: List[Report], s: String, p: String) = {
@if(reports.length > 1) {
@p
} else {
@s
}
}

@highlight_expiration(expirationDate: OffsetDateTime) = {
@if(Duration.between(OffsetDateTime.now(), expirationDate).getSeconds <= 604800) {
<strong style="color: tomato">@expirationDate.format(java.time.format.DateTimeFormatter.ofPattern("dd/MM/yyyy"))</strong>
} else {
<strong>@expirationDate.format(java.time.format.DateTimeFormatter.ofPattern("dd/MM/yyyy"))</strong>
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
</p>

<p>
Pour apporter une réponse, allez dans le détail de chaque signalement et cliquez sur le bouton "Répondre à ce signalement".
Pour apporter une réponse, allez dans le détail de chaque signalement et cliquez sur le bouton "Répondre".
</p>

<p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
</ul>

<p>
Pour apporter une réponse, allez sur votre Espace Professionnel. Dans le détail de chaque signalement, cliquez sur le bouton “Apporter une réponse à ce signalement”.
Pour apporter une réponse, allez sur votre Espace Professionnel. Dans le détail de chaque signalement, cliquez sur le bouton “Répondre”.
</p>

<p>
Expand Down

0 comments on commit 51e77da

Please sign in to comment.