diff --git a/app/controllers/WebsiteController.scala b/app/controllers/WebsiteController.scala index f278efc2..ea19c609 100644 --- a/app/controllers/WebsiteController.scala +++ b/app/controllers/WebsiteController.scala @@ -137,7 +137,7 @@ class WebsiteController( errors => Future.successful(BadRequest(JsError.toJson(errors))), company => websitesOrchestrator - .updateCompany(websiteId, company, request.identity) + .updateCompany(websiteId, company, Some(request.identity)) .map(websiteAndCompany => Ok(Json.toJson(websiteAndCompany))) ) } diff --git a/app/loader/SignalConsoApplicationLoader.scala b/app/loader/SignalConsoApplicationLoader.scala index e44aa098..a5886ff9 100644 --- a/app/loader/SignalConsoApplicationLoader.scala +++ b/app/loader/SignalConsoApplicationLoader.scala @@ -223,16 +223,16 @@ class SignalConsoComponents( val authAttemptRepository: AuthAttemptRepositoryInterface = new AuthAttemptRepository(dbConfig) val authTokenRepository: AuthTokenRepositoryInterface = new AuthTokenRepository(dbConfig) val proConnectSessionRepository: ProConnectSessionRepositoryInterface = new ProConnectSessionRepository(dbConfig) - val companyRepository: CompanyRepositoryInterface = new CompanyRepository(dbConfig) + def companyRepository: CompanyRepositoryInterface = new CompanyRepository(dbConfig) val companyActivationAttemptRepository: CompanyActivationAttemptRepositoryInterface = new CompanyActivationAttemptRepository(dbConfig) val consumerRepository: ConsumerRepositoryInterface = new ConsumerRepository(dbConfig) val emailValidationRepository: EmailValidationRepositoryInterface = new EmailValidationRepository(dbConfig) - val eventRepository: EventRepositoryInterface = new EventRepository(dbConfig) + def eventRepository: EventRepositoryInterface = new EventRepository(dbConfig) val ratingRepository: RatingRepositoryInterface = new RatingRepository(dbConfig) val influencerRepository: InfluencerRepositoryInterface = new InfluencerRepository(dbConfig) - val reportRepository: ReportRepositoryInterface = new ReportRepository(dbConfig) + def reportRepository: ReportRepositoryInterface = new ReportRepository(dbConfig) val reportMetadataRepository: ReportMetadataRepositoryInterface = new ReportMetadataRepository(dbConfig) val bookmarkRepository: BookmarkRepositoryInterface = new BookmarkRepository(dbConfig) val reportNotificationBlockedRepository: ReportNotificationBlockedRepositoryInterface = @@ -241,9 +241,9 @@ class SignalConsoComponents( new ResponseConsumerReviewRepository(dbConfig) val reportEngagementReviewRepository: ReportEngagementReviewRepositoryInterface = new ReportEngagementReviewRepository(dbConfig) - val reportFileRepository: ReportFileRepositoryInterface = new ReportFileRepository(dbConfig) + def reportFileRepository: ReportFileRepositoryInterface = new ReportFileRepository(dbConfig) val subscriptionRepository: SubscriptionRepositoryInterface = new SubscriptionRepository(dbConfig) - val userRepository: UserRepositoryInterface = new UserRepository(dbConfig, passwordHasherRegistry) + def userRepository: UserRepositoryInterface = new UserRepository(dbConfig, passwordHasherRegistry) val websiteRepository: WebsiteRepositoryInterface = new WebsiteRepository(dbConfig) val socialNetworkRepository: SocialNetworkRepositoryInterface = new SocialNetworkRepository(dbConfig) diff --git a/app/orchestrators/ImportOrchestrator.scala b/app/orchestrators/ImportOrchestrator.scala index f54d6343..b4597e4a 100644 --- a/app/orchestrators/ImportOrchestrator.scala +++ b/app/orchestrators/ImportOrchestrator.scala @@ -131,8 +131,12 @@ class ImportOrchestrator( companyId = Some(company.id), isMarketplace = true ) - updatedWebsite <- websitesOrchestrator.updateIdentification(websiteToUpdate, user) - _ <- websitesOrchestrator.updatePreviousReportsAssociatedToWebsite(website.host, company, user.id) + updatedWebsite <- websitesOrchestrator.updateIdentification(websiteToUpdate, Some(user)) + _ <- websitesOrchestrator.updatePreviousReportsAssociatedToWebsite( + website.host, + company, + Some(user.id) + ) } yield updatedWebsite } case Nil => @@ -145,7 +149,7 @@ class ImportOrchestrator( ) for { createdWebsite <- websiteRepository.create(website) - _ <- websitesOrchestrator.updatePreviousReportsAssociatedToWebsite(website.host, company, user.id) + _ <- websitesOrchestrator.updatePreviousReportsAssociatedToWebsite(website.host, company, Some(user.id)) } yield createdWebsite } diff --git a/app/orchestrators/ReportOrchestrator.scala b/app/orchestrators/ReportOrchestrator.scala index 4b484e7d..cc22875a 100644 --- a/app/orchestrators/ReportOrchestrator.scala +++ b/app/orchestrators/ReportOrchestrator.scala @@ -618,14 +618,14 @@ class ReportOrchestrator( updatedReport <- updateReportCompany( existingReport, reportCompany, - requestingUserId + Some(requestingUserId) ) } yield updatedReport def updateReportCompanyForWebsite( existingReport: Report, reportCompany: ReportCompany, - adminUserId: UUID + adminUserId: Option[UUID] ) = if (isReportTooOld(existingReport)) { logger.debug(s"Report ${existingReport.id} is too old to be updated") @@ -636,7 +636,7 @@ class ReportOrchestrator( private def updateReportCompany( existingReport: Report, reportCompany: ReportCompany, - adminUserId: UUID + adminUserId: Option[UUID] ): Future[Report] = { val updateDateTime = OffsetDateTime.now() @@ -687,9 +687,9 @@ class ReportOrchestrator( UUID.randomUUID(), Some(updatedReport.id), Some(company.id), - Some(adminUserId), + adminUserId, updateDateTime, - Constants.EventType.ADMIN, + if (adminUserId.isDefined) Constants.EventType.ADMIN else Constants.EventType.SYSTEM, Constants.ActionEvent.REPORT_COMPANY_CHANGE, stringToDetailsJsValue( s"Entreprise précédente : Siret ${existingReport.companySiret diff --git a/app/orchestrators/WebsitesOrchestrator.scala b/app/orchestrators/WebsitesOrchestrator.scala index f9d6cb88..f93cecdd 100644 --- a/app/orchestrators/WebsitesOrchestrator.scala +++ b/app/orchestrators/WebsitesOrchestrator.scala @@ -64,7 +64,7 @@ class WebsitesOrchestrator( identificationStatus = IdentificationStatus.Identified ) createdWebsite <- repository.create(website) - _ <- updatePreviousReportsAssociatedToWebsite(website.host, createdCompany, user.id) + _ <- updatePreviousReportsAssociatedToWebsite(website.host, createdCompany, Some(user.id)) } yield createdWebsite def searchByHost(host: String): Future[Seq[Country]] = @@ -135,7 +135,7 @@ class WebsitesOrchestrator( .getOrElse(Future.successful(None)) _ = logger.debug(s"Company Siret is ${maybeCompany.map(_.siret)}") _ <- maybeCompany - .map(company => updatePreviousReportsAssociatedToWebsite(website.host, company, user.id)) + .map(company => updatePreviousReportsAssociatedToWebsite(website.host, company, Some(user.id))) .getOrElse(Future.unit) } yield () } else Future.unit @@ -152,7 +152,11 @@ class WebsitesOrchestrator( } yield website } - def updateCompany(websiteId: WebsiteId, companyToAssign: CompanyCreation, user: User): Future[WebsiteAndCompany] = + def updateCompany( + websiteId: WebsiteId, + companyToAssign: CompanyCreation, + user: Option[User] + ): Future[WebsiteAndCompany] = for { company <- { logger.debug(s"Updating website (id ${websiteId}) with company siret : ${companyToAssign.siret}") @@ -171,7 +175,7 @@ class WebsitesOrchestrator( companyId = Some(company.id) ) updatedWebsite <- updateIdentification(websiteToUpdate, user) - _ <- updatePreviousReportsAssociatedToWebsite(website.host, company, user.id) + _ <- updatePreviousReportsAssociatedToWebsite(website.host, company, user.map(_.id)) } yield WebsiteAndCompany.toApi(updatedWebsite, Some(company)) def updateCompanyCountry(websiteId: WebsiteId, companyCountry: String, user: User): Future[WebsiteAndCompany] = for { @@ -188,17 +192,17 @@ class WebsitesOrchestrator( companyCountry = Some(companyCountry), companyId = None ) - updatedWebsite <- updateIdentification(websiteToUpdate, user) + updatedWebsite <- updateIdentification(websiteToUpdate, Some(user)) } yield WebsiteAndCompany.toApi(updatedWebsite, maybeCompany = None) - def updateIdentification(website: Website, user: User): Future[Website] = { + def updateIdentification(website: Website, user: Option[User]): Future[Website] = { logger.debug(s"Removing other websites with the same host : ${website.host}") for { _ <- repository .removeOtherNonIdentifiedWebsitesWithSameHost(website) _ = logger.debug(s"updating identification status when Admin is updating identification") websiteToUpdate = - if (UserRole.isAdmin(user.userRole)) website.copy(identificationStatus = Identified) else website + if (user.map(_.userRole).forall(UserRole.isAdmin)) website.copy(identificationStatus = Identified) else website _ = logger.debug(s"Website to update : ${websiteToUpdate}") updatedWebsite <- update(websiteToUpdate) _ = logger.debug(s"Website company country successfully updated") @@ -264,7 +268,7 @@ class WebsitesOrchestrator( def updatePreviousReportsAssociatedToWebsite( websiteHost: String, company: Company, - userId: UUID + userId: Option[UUID] ): Future[Unit] = { val reportCompany = ReportCompany( name = company.name, diff --git a/app/tasks/website/SiretExtractionTask.scala b/app/tasks/website/SiretExtractionTask.scala index dcc5c05a..d2057db9 100644 --- a/app/tasks/website/SiretExtractionTask.scala +++ b/app/tasks/website/SiretExtractionTask.scala @@ -35,13 +35,18 @@ class SiretExtractionTask( override val taskSettings: TaskSettings = FrequentTaskSettings(interval = 1.hour) - def matchingExtraction(company: Company, extractions: List[SiretExtractionApi]): Option[CompanySearchResult] = { - val a = extractions.find { extraction => - extraction.sirene match { - case Some(sirene) => sirene.siret == company.siret - case None => false + def findMatchingAndValidExtraction( + company: Company, + extractions: List[SiretExtractionApi] + ): Option[CompanySearchResult] = { + val a = extractions + .filter(extraction => extraction.siret.forall(_.valid) && extraction.siren.forall(_.valid)) + .find { extraction => + extraction.sirene match { + case Some(sirene) => sirene.siret == company.siret && company.isOpen + case None => false + } } - } a.flatMap(_.sirene) } @@ -63,23 +68,28 @@ class SiretExtractionTask( } } ) + // Sauvegarde des résultats en DB _ <- results.traverse { case (_, extraction) => siretExtractionRepository.insertOrReplace(extraction) } - test = results.collect { - case (Website(id, _, _, _, _, Some(companyId), _, _, _), ExtractionResultApi(_, _, _, Some(l))) => - (id, companyId, l) + // Association automatique si : + // - Un siret trouvé est valide et présent dans SIRENE + // - Le conso a fourni la même entreprise + // - L'entreprise est ouverte + companyIdsAndExtractions = results.collect { + case (Website(id, _, _, _, _, Some(companyId), _, _, _), ExtractionResultApi(_, _, _, Some(extractions))) => + (id, companyId, extractions) } - test2 <- test.traverse { case (websiteId, companyId, l) => + companyAndExtractions <- companyIdsAndExtractions.traverse { case (websiteId, companyId, extractions) => companyRepository .get(companyId) .flatMap(_.liftTo[Future](CompanyNotFound(companyId))) - .map(company => (websiteId, company, l)) + .map(company => (websiteId, company, extractions)) } - test3 = test2.flatMap { case (websiteId, company, extractions) => - matchingExtraction(company, extractions).map(a => websiteId -> a) + websiteIdAndFoundCompany = companyAndExtractions.flatMap { case (websiteId, company, extractions) => + findMatchingAndValidExtraction(company, extractions).map(companySearchResult => websiteId -> companySearchResult) } - _ <- test3.traverse { case (websiteId, companySearchResult) => - val a = CompanyCreation( + _ <- websiteIdAndFoundCompany.traverse { case (websiteId, companySearchResult) => + val companyCreation = CompanyCreation( siret = companySearchResult.siret, name = companySearchResult.name.getOrElse(""), address = companySearchResult.address, @@ -91,7 +101,7 @@ class SiretExtractionTask( commercialName = companySearchResult.commercialName, establishmentCommercialName = companySearchResult.establishmentCommercialName ) - websitesOrchestrator.updateCompany(websiteId, a, null) // TODO + websitesOrchestrator.updateCompany(websiteId, companyCreation, None) } } yield () } diff --git a/test/controllers/ReportToExternalControllerSpec.scala b/test/controllers/ReportToExternalControllerSpec.scala index d05f88de..530bc366 100644 --- a/test/controllers/ReportToExternalControllerSpec.scala +++ b/test/controllers/ReportToExternalControllerSpec.scala @@ -122,8 +122,8 @@ class ReportToExternalControllerSpec(implicit ee: ExecutionEnv) override def load(context: ApplicationLoader.Context): Application = { components = new SignalConsoComponents(context) { - override val reportRepository: ReportRepositoryInterface = mockReportRepository - override val reportFileRepository: ReportFileRepositoryInterface = mockReportFileRepository + override def reportRepository: ReportRepositoryInterface = mockReportRepository + override def reportFileRepository: ReportFileRepositoryInterface = mockReportFileRepository override def configuration: Configuration = super.configuration } components.application diff --git a/test/controllers/report/GetReportSpec.scala b/test/controllers/report/GetReportSpec.scala index 78a9b9fd..e6df304a 100644 --- a/test/controllers/report/GetReportSpec.scala +++ b/test/controllers/report/GetReportSpec.scala @@ -416,14 +416,14 @@ trait GetReportContext extends AppSpec { override def load(context: ApplicationLoader.Context): Application = { components = new SignalConsoComponents(context) { - override val reportRepository: ReportRepositoryInterface = mockReportRepository - override val companyRepository: CompanyRepositoryInterface = mockCompanyRepository - override val reportFileRepository: ReportFileRepositoryInterface = mockReportFileRepository + override def reportRepository: ReportRepositoryInterface = mockReportRepository + override def companyRepository: CompanyRepositoryInterface = mockCompanyRepository + override def reportFileRepository: ReportFileRepositoryInterface = mockReportFileRepository override lazy val mailRetriesService: MailRetriesService = mockMailRetriesService - override val eventRepository: EventRepositoryInterface = mockEventRepository + override def eventRepository: EventRepositoryInterface = mockEventRepository override def companiesVisibilityOrchestrator: CompaniesVisibilityOrchestrator = mockCompaniesVisibilityOrchestrator - override val userRepository: UserRepositoryInterface = mockUserRepository + override def userRepository: UserRepositoryInterface = mockUserRepository override def configuration: Configuration = Configuration( "slick.dbs.default.db.connectionPool" -> "disabled", diff --git a/test/tasks/account/InactiveDgccrfAccountReminderTaskSpec.scala b/test/tasks/account/InactiveDgccrfAccountReminderTaskSpec.scala index 85e5c142..20d3054b 100644 --- a/test/tasks/account/InactiveDgccrfAccountReminderTaskSpec.scala +++ b/test/tasks/account/InactiveDgccrfAccountReminderTaskSpec.scala @@ -43,9 +43,9 @@ class InactiveDgccrfAccountReminderTaskSpec(implicit ee: ExecutionEnv) override lazy val mailRetriesService: MailRetriesService = mockMailRetriesService - override val eventRepository: EventRepositoryInterface = mockEventRepository + override def eventRepository: EventRepositoryInterface = mockEventRepository - override val userRepository: UserRepositoryInterface = mockUserRepository + override def userRepository: UserRepositoryInterface = mockUserRepository override def configuration: Configuration = Configuration( "slick.dbs.default.db.connectionPool" -> "disabled", diff --git a/test/tasks/website/SiretExtractionTaskSpec.scala b/test/tasks/website/SiretExtractionTaskSpec.scala new file mode 100644 index 00000000..7a8bea35 --- /dev/null +++ b/test/tasks/website/SiretExtractionTaskSpec.scala @@ -0,0 +1,155 @@ +package tasks.website + +import models.company.CompanyCreation +import models.website.IdentificationStatus.Identified +import models.website.IdentificationStatus.NotIdentified +import models.website.Website +import models.website.WebsiteAndCompany +import orchestrators.WebsitesOrchestrator +import org.apache.pekko.stream.Materializer +import org.specs2.concurrent.ExecutionEnv +import org.specs2.matcher.FutureMatchers +import org.specs2.mock.Mockito +import org.specs2.mutable +import services.SiretExtractorService +import sttp.client3.Response +import sttp.model.StatusCode +import tasks.company.CompanySearchResult +import utils.AppSpec +import utils.Fixtures +import utils.TaskRepositoryMock +import utils.TestApp + +import scala.concurrent.Future + +class SiretExtractionTaskSpec(implicit ee: ExecutionEnv) + extends mutable.Specification + with AppSpec + with Mockito + with FutureMatchers { + + val (app, components) = TestApp.buildApp() + implicit val mat: Materializer = app.materializer + + val taskRepositoryMock = new TaskRepositoryMock() + + "SiretExtractionTask" should { + "correctly save and associate websites" in { + val siretExtractorServiceMock = mock[SiretExtractorService] + val websitesOrchestratorMock = mock[WebsitesOrchestrator] + + val service = new SiretExtractionTask( + app.actorSystem, + components.applicationConfiguration.task, + taskRepositoryMock, + components.siretExtractionRepository, + siretExtractorServiceMock, + websitesOrchestratorMock, + components.companyRepository + ) + + val company = Fixtures.genCompany.sample.get + val unusedCompany = Fixtures.genCompany.sample.get + val companySearchResult = + CompanySearchResult.fromCompany(company, Website(host = "", companyCountry = None, companyId = None)) + val website = Fixtures + .genWebsite() + .sample + .get + .copy( + host = "test2.com", + companyCountry = None, + companyId = Some(company.id), + identificationStatus = NotIdentified + ) + + siretExtractorServiceMock.extractSiret("test.com") returns Future.successful( + Response( + Right( + ExtractionResultApi( + website = "test.com", + status = "success", + error = None, + extractions = Some( + List( + SiretExtractionApi( + Some(SiretApi(company.siret.value, true)), + None, + List.empty, + Some(companySearchResult) + ) + ) + ) + ) + ), + StatusCode.Ok + ) + ) + + siretExtractorServiceMock.extractSiret("test2.com") returns Future.successful( + Response( + Right( + ExtractionResultApi( + website = "test2.com", + status = "success", + error = None, + extractions = Some( + List( + SiretExtractionApi( + Some(SiretApi(company.siret.value, true)), + None, + List.empty, + Some(companySearchResult) + ) + ) + ) + ) + ), + StatusCode.Ok + ) + ) + + websitesOrchestratorMock.updateCompany( + website.id, + CompanyCreation( + siret = companySearchResult.siret, + name = companySearchResult.name.getOrElse(""), + address = companySearchResult.address, + activityCode = companySearchResult.activityCode, + isHeadOffice = Some(companySearchResult.isHeadOffice), + isOpen = Some(companySearchResult.isOpen), + isPublic = Some(companySearchResult.isPublic), + brand = companySearchResult.brand, + commercialName = companySearchResult.commercialName, + establishmentCommercialName = companySearchResult.establishmentCommercialName + ), + None + ) returns Future.successful(WebsiteAndCompany.toApi(website, Some(company))) + + for { + _ <- components.companyRepository.create(company) + _ <- components.companyRepository.create(unusedCompany) + _ <- components.websiteRepository.create(Website(host = "test.com", companyCountry = None, companyId = None)) + _ <- components.websiteRepository.create(website) + _ <- components.websiteRepository.create( + Website( + host = "test3.com", + companyCountry = None, + companyId = Some(unusedCompany.id), + identificationStatus = Identified + ) + ) + b1 <- components.siretExtractionRepository.getByHost("test.com") + b2 <- components.siretExtractionRepository.getByHost("test2.com") + b3 <- components.siretExtractionRepository.getByHost("test3.com") + _ <- service.runTask() + a1 <- components.siretExtractionRepository.getByHost("test.com") + a2 <- components.siretExtractionRepository.getByHost("test2.com") + a3 <- components.siretExtractionRepository.getByHost("test3.com") + } yield (b1 should beNone).and(a1.isDefined shouldEqual true) and + (b2 should beNone).and(a2.isDefined shouldEqual true) and + (b3 should beNone).and(a3 should beNone) + } + } + +}