Skip to content

Commit

Permalink
Ajoute la notion de réseau
Browse files Browse the repository at this point in the history
  • Loading branch information
niladic committed Jan 22, 2025
1 parent 792f26c commit 2cc1044
Show file tree
Hide file tree
Showing 20 changed files with 305 additions and 158 deletions.
1 change: 1 addition & 0 deletions app/controllers/ApiController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ case class ApiController @Inject() (
.toList,
organisationId = Organisation.franceServicesId.some,
email = newLine.email.map(StringHelper.commonStringInputNormalization),
isInFranceServicesNetwork = true,
publicNote = none,
internalSupportComment = newLine.internalSupportComment
.map(StringHelper.commonStringInputNormalization),
Expand Down
255 changes: 145 additions & 110 deletions app/controllers/ApplicationController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -141,12 +141,17 @@ case class ApplicationController @Inject() (
)
}

/** Groups with instructors available to the current user */

private def fetchGroupsWithInstructors(
areaId: UUID,
currentUser: User,
rights: Authorization.UserRights
rights: Authorization.UserRights,
currentUserGroups: List[UserGroup]
): Future[(List[UserGroup], List[User], List[User])] = {
val groupsOfAreaFuture = userGroupService.byArea(areaId)
val hasFranceServicesAccess = currentUserGroups.exists(_.isInFranceServicesNetwork)
val groupsOfAreaFuture =
userGroupService.byArea(areaId, excludeFranceServicesNetwork = !hasFranceServicesAccess)
groupsOfAreaFuture.map { groupsOfArea =>
val visibleGroups = filterVisibleGroups(areaId, currentUser, rights)(groupsOfArea)
val usersInThoseGroups = userService.byGroupIds(visibleGroups.map(_.id))
Expand Down Expand Up @@ -199,21 +204,25 @@ case class ApplicationController @Inject() (
userGroupService
.byIdsFuture(request.currentUser.groupIds)
.flatMap(userGroups =>
fetchGroupsWithInstructors(currentArea.id, request.currentUser, request.rights).map {
case (groupsOfAreaWithInstructor, instructorsOfGroups, coworkers) =>
val categories = organisationService.categories
Ok(
views.html.createApplication(request.currentUser, request.rights, currentArea)(
userGroups,
instructorsOfGroups,
groupsOfAreaWithInstructor,
coworkers,
readSharedAccountUserSignature(request.session),
canCreatePhoneMandat = currentArea === Area.calvados,
categories,
ApplicationFormData.form(request.currentUser)
)
fetchGroupsWithInstructors(
currentArea.id,
request.currentUser,
request.rights,
userGroups
).map { case (groupsOfAreaWithInstructor, instructorsOfGroups, coworkers) =>
val categories = organisationService.categories
Ok(
views.html.createApplication(request.currentUser, request.rights, currentArea)(
userGroups,
instructorsOfGroups,
groupsOfAreaWithInstructor,
coworkers,
readSharedAccountUserSignature(request.session),
canCreatePhoneMandat = currentArea === Area.calvados,
categories,
ApplicationFormData.form(request.currentUser)
)
)
}
)
)
Expand Down Expand Up @@ -275,25 +284,29 @@ case class ApplicationController @Inject() (
userGroupService
.byIdsFuture(request.currentUser.groupIds)
.flatMap(userGroups =>
fetchGroupsWithInstructors(currentArea.id, request.currentUser, request.rights).map {
case (groupsOfAreaWithInstructor, instructorsOfGroups, coworkers) =>
val message =
"Erreur lors de l'envoi de fichiers. Cette erreur est possiblement temporaire."
BadRequest(
views.html
.createApplication(request.currentUser, request.rights, currentArea)(
userGroups,
instructorsOfGroups,
groupsOfAreaWithInstructor,
coworkers,
None,
canCreatePhoneMandat = currentArea === Area.calvados,
organisationService.categories,
form,
Nil,
)
)
.flashing("application-error" -> message)
fetchGroupsWithInstructors(
currentArea.id,
request.currentUser,
request.rights,
userGroups
).map { case (groupsOfAreaWithInstructor, instructorsOfGroups, coworkers) =>
val message =
"Erreur lors de l'envoi de fichiers. Cette erreur est possiblement temporaire."
BadRequest(
views.html
.createApplication(request.currentUser, request.rights, currentArea)(
userGroups,
instructorsOfGroups,
groupsOfAreaWithInstructor,
coworkers,
None,
canCreatePhoneMandat = currentArea === Area.calvados,
organisationService.categories,
form,
Nil,
)
)
.flashing("application-error" -> message)
}
)
} { files =>
Expand All @@ -303,7 +316,12 @@ case class ApplicationController @Inject() (
userGroupService
.byIdsFuture(request.currentUser.groupIds)
.flatMap(userGroups =>
fetchGroupsWithInstructors(currentArea.id, request.currentUser, request.rights)
fetchGroupsWithInstructors(
currentArea.id,
request.currentUser,
request.rights,
userGroups
)
.map { case (groupsOfAreaWithInstructor, instructorsOfGroups, coworkers) =>
eventService.log(
EventType.ApplicationCreationInvalid,
Expand Down Expand Up @@ -358,6 +376,8 @@ case class ApplicationController @Inject() (
if infoName.trim.nonEmpty && infoValue.trim.nonEmpty =>
infoName.trim -> infoValue.trim
}

val isInFranceServicesNetwork = creatorGroup.exists(_.isInFranceServicesNetwork)
val application = Application(
applicationId,
Time.nowParis(),
Expand All @@ -380,7 +400,8 @@ case class ApplicationController @Inject() (
category = applicationData.category,
mandatType = Application.MandatType.Paper.some,
mandatDate = Some(applicationData.mandatDate),
invitedGroupIdsAtCreation = applicationData.groups
invitedGroupIdsAtCreation = applicationData.groups,
isInFranceServicesNetwork = isInFranceServicesNetwork,
)
if (applicationService.createApplication(application)) {
notificationsService.newApplication(application)
Expand Down Expand Up @@ -1120,33 +1141,41 @@ case class ApplicationController @Inject() (

private def usersWhoCanBeInvitedOn(application: Application, currentAreaId: UUID)(implicit
request: RequestWithUserData[_]
): Future[List[User]] =
(if (request.currentUser.expert || request.currentUser.admin) {
val creator = userService.byId(application.creatorUserId, includeDisabled = true)
val creatorGroups: Set[UUID] = creator.toList.flatMap(_.groupIds).toSet
userGroupService.byArea(currentAreaId).map { groupsOfArea =>
userService
.byGroupIds(groupsOfArea.map(_.id))
.filter(user => user.instructor || user.groupIds.toSet.intersect(creatorGroups).nonEmpty)
}
} else {
// 1. coworkers
val coworkers = Future(userService.byGroupIds(request.currentUser.groupIds))
// 2. coworkers of instructors that are already on the application
// these will mostly be the ones that have been added as users after
// the application has been sent.
val instructorsCoworkers = {
val invitedUsers: List[User] =
userService.byIds(application.invitedUsers.keys.toList, includeDisabled = true)
val groupsOfInvitedUsers: Set[UUID] = invitedUsers.flatMap(_.groupIds).toSet
userGroupService.byArea(application.area).map { groupsOfArea =>
val invitedGroups: Set[UUID] =
groupsOfInvitedUsers.intersect(groupsOfArea.map(_.id).toSet)
userService.byGroupIds(invitedGroups.toList).filter(_.instructor)
}
}
coworkers.combine(instructorsCoworkers)
})
): Future[List[User]] = {
val excludeFranceServicesNetwork = !application.isInFranceServicesNetwork
if (request.currentUser.expert || request.currentUser.admin) {
val creator = userService.byId(application.creatorUserId, includeDisabled = true)
val creatorGroups: Set[UUID] = creator.toList.flatMap(_.groupIds).toSet
userGroupService
.byArea(currentAreaId, excludeFranceServicesNetwork = excludeFranceServicesNetwork)
.map { groupsOfArea =>
userService
.byGroupIds(groupsOfArea.map(_.id))
.filter(user =>
user.instructor || user.groupIds.toSet.intersect(creatorGroups).nonEmpty
)
}
} else {
// 1. coworkers
val coworkers = Future(userService.byGroupIds(request.currentUser.groupIds))
// 2. coworkers of instructors that are already on the application
// these will mostly be the ones that have been added as users after
// the application has been sent.
val instructorsCoworkers = {
val invitedUsers: List[User] =
userService.byIds(application.invitedUsers.keys.toList, includeDisabled = true)
val groupsOfInvitedUsers: Set[UUID] = invitedUsers.flatMap(_.groupIds).toSet
userGroupService
.byArea(application.area, excludeFranceServicesNetwork = excludeFranceServicesNetwork)
.map { groupsOfArea =>
val invitedGroups: Set[UUID] =
groupsOfInvitedUsers.intersect(groupsOfArea.map(_.id).toSet)
userService.byGroupIds(invitedGroups.toList).filter(_.instructor)
}
}
coworkers.combine(instructorsCoworkers)
}
}
.map(
_.filterNot(user =>
user.id === request.currentUser.id || application.invitedUsers.contains(user.id)
Expand All @@ -1162,19 +1191,22 @@ case class ApplicationController @Inject() (
userService.byIds(application.invitedUsers.keys.toList, includeDisabled = true)
// Groups already present on the Application
val groupsOfInvitedUsers: Set[UUID] = invitedUsers.flatMap(_.groupIds).toSet
userGroupService.byArea(forAreaId).map { groupsOfArea =>
val groupsThatAreNotInvited =
groupsOfArea.filterNot(group => groupsOfInvitedUsers.contains(group.id))
val groupIdsWithInstructors: Set[UUID] =
userService
.byGroupIds(groupsThatAreNotInvited.map(_.id))
.filter(_.instructor)
.flatMap(_.groupIds)
.toSet
val groupsThatAreNotInvitedWithInstructor =
groupsThatAreNotInvited.filter(user => groupIdsWithInstructors.contains(user.id))
groupsThatAreNotInvitedWithInstructor.sortBy(_.name)
}
val excludeFranceServicesNetwork = !application.isInFranceServicesNetwork
userGroupService
.byArea(forAreaId, excludeFranceServicesNetwork = excludeFranceServicesNetwork)
.map { groupsOfArea =>
val groupsThatAreNotInvited =
groupsOfArea.filterNot(group => groupsOfInvitedUsers.contains(group.id))
val groupIdsWithInstructors: Set[UUID] =
userService
.byGroupIds(groupsThatAreNotInvited.map(_.id))
.filter(_.instructor)
.flatMap(_.groupIds)
.toSet
val groupsThatAreNotInvitedWithInstructor =
groupsThatAreNotInvited.filter(user => groupIdsWithInstructors.contains(user.id))
groupsThatAreNotInvitedWithInstructor.sortBy(_.name)
}
}

def applicationInvitableGroups(applicationId: UUID, areaId: UUID): Action[AnyContent] =
Expand Down Expand Up @@ -1206,41 +1238,44 @@ case class ApplicationController @Inject() (
application.answers.map(_.creatorUserID)
usersWhoCanBeInvitedOn(application, selectedAreaId).flatMap { usersWhoCanBeInvited =>
groupsWhichCanBeInvited(selectedAreaId, application).flatMap { invitableGroups =>
val filesF = EitherT(fileService.byApplicationId(application.id))
val organisationsF = EitherT(userService.usersOrganisations(applicationUsers))
(for {
files <- filesF
organisations <- organisationsF
} yield (files, organisations)).value
.map(
_.fold(
error => {
eventService.logError(error)
InternalServerError(Constants.genericError500Message)
},
{ case (files, organisations) =>
val groups = userGroupService
.byIds(usersWhoCanBeInvited.flatMap(_.groupIds))
.filter(_.areaIds.contains[UUID](selectedAreaId))
val groupsWithUsersThatCanBeInvited = groups.map { group =>
group -> usersWhoCanBeInvited.filter(_.groupIds.contains[UUID](group.id))
userGroupService.byIdsFuture(request.currentUser.groupIds).flatMap { userGroups =>
val filesF = EitherT(fileService.byApplicationId(application.id))
val organisationsF = EitherT(userService.usersOrganisations(applicationUsers))
(for {
files <- filesF
organisations <- organisationsF
} yield (files, organisations)).value
.map(
_.fold(
error => {
eventService.logError(error)
InternalServerError(Constants.genericError500Message)
},
{ case (files, organisations) =>
val groups = userGroupService
.byIds(usersWhoCanBeInvited.flatMap(_.groupIds))
.filter(_.areaIds.contains[UUID](selectedAreaId))
val groupsWithUsersThatCanBeInvited = groups.map { group =>
group -> usersWhoCanBeInvited.filter(_.groupIds.contains[UUID](group.id))
}
toResult(
views.html.showApplication(request.currentUser, request.rights)(
userGroups,
groupsWithUsersThatCanBeInvited,
invitableGroups,
application,
form,
openedTab,
selectedArea,
readSharedAccountUserSignature(request.session),
files,
organisations
)
).withHeaders(CACHE_CONTROL -> "no-store")
}
toResult(
views.html.showApplication(request.currentUser, request.rights)(
groupsWithUsersThatCanBeInvited,
invitableGroups,
application,
form,
openedTab,
selectedArea,
readSharedAccountUserSignature(request.session),
files,
organisations
)
).withHeaders(CACHE_CONTROL -> "no-store")
}
)
)
)
}
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions app/controllers/CSVImportController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ case class CSVImportController @Inject() (
areaIds = group.group.areaIds,
organisationId = group.group.organisationId,
email = group.group.email,
isInFranceServicesNetwork = true,
publicNote = None,
internalSupportComment = None
),
Expand Down Expand Up @@ -196,6 +197,7 @@ case class CSVImportController @Inject() (
_.exists(Organisation.isValidId)
),
"email" -> optional(email),
"isInFranceServicesNetwork" -> ignored(true),
"publicNote" -> ignored(Option.empty[String]),
"internalSupportComment" -> ignored(Option.empty[String])
)(UserGroup.apply)(toTupleOpt)
Expand Down
2 changes: 2 additions & 0 deletions app/models/Application.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ case class Application(
mandatType: Option[Application.MandatType],
mandatDate: Option[String],
invitedGroupIdsAtCreation: List[UUID],
isInFranceServicesNetwork: Boolean,
personalDataWiped: Boolean = false,
) extends AgeModel {

Expand Down Expand Up @@ -253,6 +254,7 @@ case class Application(
mandatType = wiped.mandatType,
mandatDate = anonMandatDate,
invitedGroupIdsAtCreation = wiped.invitedGroupIdsAtCreation,
isInFranceServicesNetwork = wiped.isInFranceServicesNetwork,
personalDataWiped = wiped.personalDataWiped,
)
}
Expand Down
1 change: 1 addition & 0 deletions app/models/UserGroup.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ case class UserGroup(
areaIds: List[UUID],
organisationId: Option[Organisation.Id] = None,
email: Option[String] = None,
isInFranceServicesNetwork: Boolean,
// This is a note displayed to users trying to select this group
publicNote: Option[String],
// This is a comment only visible by the admins
Expand Down
3 changes: 3 additions & 0 deletions app/models/dataModels.scala
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ object dataModels {
application.mandatType.map(dataModels.Application.MandatType.dataModelSerialization),
mandatDate = application.mandatDate,
invitedGroupIds = application.invitedGroupIdsAtCreation,
isInFranceServicesNetwork = application.isInFranceServicesNetwork,
personalDataWiped = application.personalDataWiped
)

Expand Down Expand Up @@ -329,6 +330,7 @@ object dataModels {
mandatType: Option[String],
mandatDate: Option[String],
invitedGroupIds: List[UUID],
isInFranceServicesNetwork: Boolean,
personalDataWiped: Boolean,
) {

Expand Down Expand Up @@ -360,6 +362,7 @@ object dataModels {
mandatType = mandatType.flatMap(Application.MandatType.dataModelDeserialization),
mandatDate = mandatDate,
invitedGroupIdsAtCreation = invitedGroupIds,
isInFranceServicesNetwork = isInFranceServicesNetwork,
personalDataWiped = personalDataWiped,
)
}
Expand Down
Loading

0 comments on commit 2cc1044

Please sign in to comment.