<?php
namespace App\Controller;
use App\Entity\ClauseScoiety;
use App\Entity\Client;
use App\Entity\DomiciliationAddress;
use App\Entity\DocumentSociety;
use App\Entity\Files;
use App\Entity\President;
use App\Entity\Signatures;
use App\Entity\SirenEnterprise;
use App\Entity\Society;
use App\Entity\UserClauseSelection;
use App\Entity\UserSocietyActivities;
use App\Events\DocumentEvent;
use App\Form\AssociesType;
use App\Form\PresidentType;
use App\Form\SocietyConditionsType;
use App\Form\SocietyIndicatorType;
use App\Form\SocietyType;
use App\Manager\InpiManager;
use App\Repository\ClauseScoietyRepository;
use App\Repository\ClientRepository;
use App\Repository\DocumentSocietyRepository;
use App\Repository\FilesRepository;
use App\Repository\IbanRepository;
use App\Repository\SirenEnterpriseRepository;
use App\Repository\SocietyRepository;
use App\Repository\UserClauseSelectionRepository;
use App\Repository\UserRepository;
use App\Service\MailService;
use App\Service\S3Service;
use Doctrine\ORM\EntityManagerInterface;
use Knp\Component\Pager\PaginatorInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\GenericEvent;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\Routing\Annotation\Route;
use PhpOffice\PhpWord\Shared\ZipArchive;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\HttpClient\HttpClientInterface;
/**
* @Route("/society")
*/
class SocietyController extends AbstractController
{
private $mailService;
private $documentSocietyRepository;
private $entityManager;
private $s3Service;
private $httpClient;
public function __construct(MailService $mailService, DocumentSocietyRepository $documentSocietyRepository, EntityManagerInterface $entityManager, S3Service $s3Service , HttpClientInterface $httpClient)
{
$this->mailService = $mailService;
$this->documentSocietyRepository = $documentSocietyRepository;
$this->entityManager = $entityManager;
$this->s3Service = $s3Service;
$this->httpClient = $httpClient; }
/**
* @Route("/", name="society_index", methods={"GET"})
*/
public function index(Request $request, PaginatorInterface $paginator, SocietyRepository $societyRepository, UserRepository $userRepository)
{
$connectedUser = $this->getUser();
if (!$connectedUser) {
throw $this->createAccessDeniedException('Vous devez être connecté pour accéder à cette page.');
}
$collaborator = $request->query->getInt('collaborator') ?: null;
$postalCode = trim(strip_tags((string) $request->query->get('postal_code', ''))) ?: null;
$address = trim(strip_tags((string) $request->query->get('address', ''))) ?: null;
$name = trim(strip_tags((string) $request->query->get('name', ''))) ?: null;
$number = trim(strip_tags((string) $request->query->get('number', ''))) ?: null;
$created = trim(strip_tags((string) $request->query->get('created', ''))) ?: null;
$users = [];
$collaborators = [];
$isClient = false;
$isCollaborator = false;
if ($this->isGranted('ROLE_ADMIN')) {
$users = $userRepository->findAll();
} elseif ($this->isGranted('ROLE_CLIENT')) {
$users = $userRepository->findCollaboratorsByClient($connectedUser);
$collaborators = array_map(function ($user) {
return $user->getId();
}, $users);
$isClient = true;
} elseif ($this->isGranted('ROLE_COLLABORATEUR')) {
// If the user is a collaborator, show only their own societies
$collaborators = [$connectedUser->getId()];
$isCollaborator = true;
}
$queryBuilder = $societyRepository->findByFiltersQueryBuilder(
$collaborator, $postalCode, $address, $name, $number, $connectedUser->getClient(), $collaborators, $isClient, $isCollaborator, $created
);
// Pagination côté serveur avec Knp Paginator
$pagination = $paginator->paginate(
$queryBuilder,
$request->query->getInt('page', 1),
12 // Nombre d'items par page
);
// Calcul des statistiques significatives
$allSocieties = $queryBuilder->getQuery()->getResult();
// Sociétés créées ce mois
$startOfMonth = new \DateTime('first day of this month 00:00:00');
$createdThisMonth = array_filter($allSocieties, function($society) use ($startOfMonth) {
return $society->getCreated() && $society->getCreated() >= $startOfMonth;
});
// Sociétés modifiées dans les 7 derniers jours
$sevenDaysAgo = new \DateTime('-7 days');
$recentlyUpdated = array_filter($allSocieties, function($society) use ($sevenDaysAgo) {
return $society->getUpdated() && $society->getUpdated() >= $sevenDaysAgo;
});
// Sociétés en attente (status = 'created')
$pendingSocieties = array_filter($allSocieties, function($society) {
return $society->getStatus() === Society::STATUS_CREATED;
});
return $this->render('society/index.html.twig', [
'pagination' => $pagination,
'users' => $users,
'stats' => [
'createdThisMonth' => count($createdThisMonth),
'recentlyUpdated' => count($recentlyUpdated),
'pending' => count($pendingSocieties),
],
]);
}
/**
* @Route("/new", name="society_new", methods={"GET", "POST"})
*/
public
function new(Request $request, EntityManagerInterface $entityManager, RequestStack $requestStack): Response
{
$society = new Society();
$user = $this->getUser();
/**
* @var Client $client
*/
$client = $user->getClient();
// Pré-remplir les infos de domiciliation depuis le client existant
if ($client) {
$domClient = new \App\Entity\Client();
$domClient->setName($client->getName());
$domClient->setSocietyForme($client->getSocietyForme());
$domClient->setSiren($client->getSiren());
$domClient->setAgreementNumber($client->getAgreementNumber());
$domClient->setSocietySiege($client->getSocietySiege());
$domClient->setCp($client->getCp());
$domClient->setVille($client->getVille());
$domClient->setSocietyCapitalChiffre($client->getSocietyCapitalChiffre());
$domClient->setSocietyCapitalLettre($client->getSocietyCapitalLettre());
$domClient->setQualityRepresentant($client->getQualityRepresentant());
$domClient->setCivility($client->getCivility());
$domClient->setRepresantantLastName($client->getRepresantantLastName());
$domClient->setRepresantantFirstName($client->getRepresantantFirstName());
$domClient->setDescriptionService($client->getDescriptionService());
$domClient->setImmeubleDescription($client->getImmeubleDescription());
$domClient->setEquipment($client->getEquipment());
$domClient->setSurfaceOffice($client->getSurfaceOffice());
$domClient->setPieceNumber($client->getPieceNumber());
$domClient->setMontantRedevance($client->getMontantRedevance());
$domClient->setDureeContrat($client->getDureeContrat());
$domClient->setDelaiDay($client->getDelaiDay());
$society->setClient($domClient);
}
$form = $this->createForm(SocietyType::class, $society);
$form->handleRequest($request);
$formAssocy = $this->createForm(AssociesType::class);
$client->getSociety()->count();
if ($form->isSubmitted() && $form->isValid()) {
$number = sprintf("%08d", $client->getSociety()->count() + 1);
$society->setCreated(new \DateTime("now"));
$society->setClient($user->getClient());
$society->setStatus(Society::STATUS_CREATED);
$society->setNumber(sprintf('%08d', $number));
$society->setUser($user);
$society->setFreeOrder(true);
// Store the selected domiciliation address reference
$domiciliationAddressId = $request->request->get('domiciliation_address_id');
if ($domiciliationAddressId) {
$domiciliationAddress = $entityManager->getRepository(DomiciliationAddress::class)->find($domiciliationAddressId);
if ($domiciliationAddress && $domiciliationAddress->getClient() === $client) {
$society->setDomiciliationAddress($domiciliationAddress);
}
}
$entityManager->persist($society);
$entityManager->flush();
$publicDirectory = $this->getParameter('pdf_directory');
$filepath = $publicDirectory . "/" . $society->getId() . "/";
if (!file_exists($filepath)) {
mkdir($filepath, 0777, true);
}
$requestStack->getSession()->set('tab', 'society');
$entityManager->flush();
return $this->redirectToRoute('society_show', ['id' => $society->getId()], Response::HTTP_SEE_OTHER);
}
$domiciliationAddresses = $client->getDomiciliationAddresses();
$yearEnd = date('j/m', strtotime('12/31'));
$now = new \DateTime();
return $this->renderForm('society/new.html.twig', [
'society' => $society,
'endDate' => $yearEnd,
'now' => date('j/m/Y'),
'form' => $form,
'formAssocy' => $formAssocy,
'client' => $user,
'domiciliationAddresses' => $domiciliationAddresses,
]);
}
/**
* @Route("/api/domiciliation-address/{id}", name="api_domiciliation_address", methods={"GET"})
*/
public function getDomiciliationAddress(int $id, EntityManagerInterface $entityManager): JsonResponse
{
$user = $this->getUser();
$client = $user->getClient();
$address = $entityManager->getRepository(DomiciliationAddress::class)->find($id);
if (!$address || $address->getClient() !== $client) {
return new JsonResponse(['error' => 'Adresse non trouvée'], Response::HTTP_NOT_FOUND);
}
return new JsonResponse([
'id' => $address->getId(),
'label' => $address->getLabel(),
'name' => $address->getName(),
'societyForme' => $address->getSocietyForme(),
'siren' => $address->getSiren(),
'agreementNumber' => $address->getAgreementNumber(),
'societySiege' => $address->getSocietySiege(),
'cp' => $address->getCp(),
'ville' => $address->getVille(),
'societyCapitalChiffre' => $address->getSocietyCapitalChiffre(),
'societyCapitalLettre' => $address->getSocietyCapitalLettre(),
'qualityRepresentant' => $address->getQualityRepresentant(),
'civility' => $address->getCivility(),
'represantantLastName' => $address->getRepresantantLastName(),
'represantantFirstName' => $address->getRepresantantFirstName(),
'descriptionService' => $address->getDescriptionService(),
'immeubleDescription' => $address->getImmeubleDescription(),
'equipment' => $address->getEquipment(),
'surfaceOffice' => $address->getSurfaceOffice(),
'pieceNumber' => $address->getPieceNumber(),
'montantRedevance' => $address->getMontantRedevance(),
'domiciliationDate' => $address->getDomiciliationDate() ? $address->getDomiciliationDate()->format('Y-m-d') : null,
'dureeContrat' => $address->getDureeContrat(),
'delaiDay' => $address->getDelaiDay(),
]);
}
/**
* @Route("/searchSiren", name="society_siren_info")
*/
public function fetchEnterpriseData(SirenEnterpriseRepository $sirenEnterpriseRepository, EntityManagerInterface $entityManager, Request $request)
{
$siren = $request->request->get('siren');
$enterprise = $sirenEnterpriseRepository->findOneBy(['siren' => $siren]);
if ($enterprise) {
$html = $this->renderView('society/enterpriseInfo.html.twig', ['data' => $enterprise->getData(), 'siren' => $siren]);
return new Response($html);
}
$apiToken = 'fa81590fc90352718e3dd057c27a9f34cd57188d83fd002f';
$url = "https://api.pappers.fr/v2/entreprise?api_token=$apiToken&siren=$siren";
$httpClient = HttpClient::create();
$response = $httpClient->request('GET', $url);
$data = [];
$apiError = null;
$statusCode = $response->getStatusCode();
if ($statusCode === 200) {
$content = $response->getContent();
$data = json_decode($content, true);
$enterprise = new SirenEnterprise();
$enterprise->setSiren($siren);
$enterprise->setData($data);
$entityManager->persist($enterprise);
$entityManager->flush();
} else {
$content = $response->getContent(false);
$errorData = json_decode($content, true);
if (isset($errorData['error'])) {
$apiError = $errorData['error'];
}
}
//normalement data hna fil edit njib associe ou bien dirigent -> getRepresentantData mech donnés mta3 l api, 5ater badel fihom
$html = $this->renderView('society/enterpriseInfo.html.twig', ['data' => $data, 'siren' => $siren, 'apiError' => $apiError]);
return new Response($html);
}
/**
* @Route("/{id}", name="society_show", methods={"GET"})
*/
public function show(Society $society, ClauseScoietyRepository $clauseScoietyRepository, IbanRepository $ibanRepository, FilesRepository $filesRepository, EntityManagerInterface $entityManager, Request $request, InpiManager $inpiManager, Security $security, UserClauseSelectionRepository $userClauseSelectionRepository, RequestStack $requestStack): Response
{
$user = $security->getUser();
if ($society->getUser() !== $user) {
return $this->render('redirection/AccessDenied.html.twig');
}
$selectedProfile = null;
$statutsBySocietyType = null;
if ($society->getType()) {
$statutsBySocietyType = $userClauseSelectionRepository->StatusSociety($user->getId(), $society->getType()->getId());
if ($society->getSelectedProfil()) {
$selectedProfile = $society->getSelectedProfil()->getClauses();
}
}
$actifTabParam = $request->query->get('actifTab');
if ($actifTabParam === 'signature') {
$actifTab = 'signature';
} else {
$tab = $requestStack->getSession()->get('tab');
if ($tab && $tab == 'society') {
$actifTab = 'society';
} else
$actifTab = 'document';
if (count($society->getDocumentSocieties()) > 0) {
$requestStack->getSession()->remove('tab');
}else{
$actifTab = 'society';
}
}
$form = $this->createForm(SocietyType::class, $society);
$formSocietyIndicator = $this->createForm(SocietyIndicatorType::class);
$firstAssocy = $society->getAssocies()->first() ?: null;
$formAssocy = $this->createForm(AssociesType::class, $firstAssocy, ['pourcentageMax' => $society->getTotalPourcentageAssocies()]);
$formPresident = $this->createForm(PresidentType::class,new President());
//dd($formPresident);
$SocietyConditions = $society->getSocietyConditions() ? $society->getSocietyConditions() : null;
$formSocietyConditions = $this->createForm(SocietyConditionsType::class, $SocietyConditions);
$form->handleRequest($request);
$files = $filesRepository->findBy(['society' => $society->getId()]);
$documents = $society->getDocumentSocieties();
//$signatairesByDocument = [];
//foreach ($documents as $document) {
//$signatairesByDocument[$document->getId()] = $document->getSociety()->getSignataires() ?? [];
//}
mb_internal_encoding("UTF-8"); // Assurez-vous que l'encodage interne est défini sur UTF-8
$societyTypeName = $society->getType() ? $society->getType()->getName() : "NC";
$decodedName = mb_convert_case($societyTypeName, MB_CASE_LOWER, "UTF-8");
$includeCommon = !str_contains($decodedName, 'sasu');
$statuts = $clauseScoietyRepository->findBySocietyType(trim($decodedName), $includeCommon);
$recipients = $society->getAssociesEmails();
//b$documentsSigned = $entityManager->getRepository(Signatures::class)->findBy(['status' => $society->getId(), 'si' => ['!=' => null]]);
return $this->render('society/show.html.twig', [
'society' => $society,
'recipients' => $recipients,
'documents' => $documents,
'signataires' => $society->getSignataires(),
'form' => $form->createView(),
'formAssocy' => $formAssocy->createView(),
'formSocietyConditions' => $formSocietyConditions->createView(),
'formPresident' => $formPresident->createView(),
'formSocietyIndicator' => $formSocietyIndicator->createView(),
'associes' => $society->getAssocies(),
'presidents' => $society->getPresidents(),
'society_indicators' => $society->getSocietyIndicators(),
'files' => $files,
'directory' => "uploads/societyDocuments/",
'statuts' => $statuts,
'statutsBySocietyType' => $statutsBySocietyType,
'selectedProfile' => $selectedProfile,
'actifTab' => $actifTab,
'domiciliationAddresses' => $user->getClient() ? $user->getClient()->getDomiciliationAddresses() : [],
]);
}
/**
* @Route("/api/society/{id}/can-update", name="api_society_can_update", methods={"GET"})
*/
public function canUpdateSocietyApi(int $id, EntityManagerInterface $em): JsonResponse
{
$society = $em->getRepository(Society::class)->find($id);
if (!$society) {
return new JsonResponse(['error' => 'Société introuvable'], 404);
}
$associes = count($society->getAssocies());
$dirigeants = count($society->getPresidents());
$canUpdate = $associes > 0 && $dirigeants > 0;
return new JsonResponse(['canUpdate' => $canUpdate]);
}
/**
* @Route("/{id}/edit", name="society_edit", methods={"GET", "POST"})
*/
public function edit(Request $request, Society $society, EntityManagerInterface $entityManager, FilesRepository $filesRepository, EventDispatcherInterface $dispatcher): Response
{
$form = $this->createForm(SocietyType::class, $society);
$form->handleRequest($request);
$publicDirectory = $this->getParameter('pdf_directory');
$filepath = $publicDirectory . "/" . $society->getId() . "/";
if (!file_exists($filepath)) {
mkdir($filepath, 0777, true);
}
if ($form->isSubmitted()) {
// Sauvegarder les champs condition_* des formulaires AJAX dans societyStatus
$allParams = $request->request->all();
$societyStatus = $society->getSocietyStatus() ?? [];
foreach ($allParams as $key => $value) {
if (strpos($key, 'condition_') === 0 && !empty($value)) {
$societyStatus[$key] = $value;
}
}
$society->setSocietyStatus($societyStatus);
// Sauvegarder les champs capitalVariable / capitalMontantMin / capitalMontantMax sur le premier associé
$associesPostData = $request->request->all('associes');
if (!empty($associesPostData)) {
$firstAssocy = $society->getAssocies()->first();
if ($firstAssocy) {
$firstAssocy->setCapitalVariable(!empty($associesPostData['capitalVariable']));
$firstAssocy->setCapitalMontantMin($associesPostData['capitalMontantMin'] ?? null);
$firstAssocy->setCapitalMontantMax($associesPostData['capitalMontantMax'] ?? null);
if (!empty($associesPostData['Periodicite'])) {
$firstAssocy->setPeriodicite($associesPostData['Periodicite']);
}
$entityManager->persist($firstAssocy);
}
} else {
// Checkbox non cochée = décochée
$firstAssocy = $society->getAssocies()->first();
if ($firstAssocy) {
$firstAssocy->setCapitalVariable(false);
$entityManager->persist($firstAssocy);
}
}
// Store the selected domiciliation address reference
$domiciliationAddressId = $request->request->get('domiciliation_address_id');
if ($domiciliationAddressId) {
$domiciliationAddress = $entityManager->getRepository(DomiciliationAddress::class)->find($domiciliationAddressId);
if ($domiciliationAddress && $domiciliationAddress->getClient() === $society->getClient()) {
$society->setDomiciliationAddress($domiciliationAddress);
}
} else {
$society->setDomiciliationAddress(null);
}
$society->setUpdated(new \DateTime("now"));
$userSocietyAcitvities = new UserSocietyActivities();
$userSocietyAcitvities->setSociety($society);
$userSocietyAcitvities->setUser($this->getUser());
$userSocietyAcitvities->setClient($society->getClient());
$userSocietyAcitvities->setUpdatedAt(new \DateTime());
$entityManager->persist($userSocietyAcitvities);
$entityManager->flush();
$dispatcher->dispatch(new GenericEvent($society, [$filepath]), DocumentEvent::SOCIETY_REMOVE_DOCUMENTS);
$dispatcher->dispatch(new GenericEvent($society, [$filepath]), DocumentEvent::SOCIETY_CREATE);
//$dispatcher->dispatch(new GenericEvent($society, [$filepath]), DocumentEvent::SOCIETY_UPDATE);
$entityManager->flush();
return $this->redirectToRoute('society_show', ['id' => $society->getId()], Response::HTTP_SEE_OTHER);
}
$user = $this->getUser();
$client = $user->getClient();
$domiciliationAddresses = $client ? $client->getDomiciliationAddresses() : [];
$files = $filesRepository->findBy(['society' => $society->getId()]);
$publicDirectory = $this->getParameter('file_directory');
return $this->renderForm('society/edit.html.twig', [
'society' => $society,
'form' => $form,
'associes' => $society->getAssocies(),
'presidents' => $society->getPresidents(),
'files' => $files,
'directory' => "uploads/societyDocuments/",
'domiciliationAddresses' => $domiciliationAddresses,
]);
}
// *****************************************************edit button ************************************************************************
/**
* @Route("/{id}/generate-documents", name="society_generate_documents", methods={"GET", "POST"})
*/
public function generate(Request $request, Society $society, EntityManagerInterface $entityManager, EventDispatcherInterface $dispatcher): Response
{
if ($society->getDocumentSocieties()) {
foreach ($society->getDocumentSocieties() as $documentSociety) {
if ($documentSociety->getSignatures()) {
foreach ($documentSociety->getSignatures() as $signature) {
$entityManager->remove($signature);
}
}
$entityManager->flush();
}
}
$publicDirectory = $this->getParameter('pdf_directory');
$filepath = $publicDirectory . "/" . $society->getId() . "/";
if (!file_exists($filepath)) {
mkdir($filepath, 0777, true);
}
//hna n3adi fil path li houwa ploads/societyPdf/
$dispatcher->dispatch(new GenericEvent($society, [$filepath]), DocumentEvent::SOCIETY_REMOVE_DOCUMENTS);
$dispatcher->dispatch(new GenericEvent($society, [$filepath]), DocumentEvent::SOCIETY_CREATE);
$dispatcher->dispatch(new GenericEvent($society, [$filepath]), DocumentEvent::SOCIETY_REMOVE_DOCUMENTS_FOLDER);
$entityManager->flush();
// Rediriger après modification
return $this->redirectToRoute('society_show', ['id' => $society->getId()], Response::HTTP_SEE_OTHER);
}
// *****************************************************edit button ************************************************************************
/**
* @Route("/{id}", name="society_delete", methods={"POST"})
*/
public function delete(Request $request, Society $society, EntityManagerInterface $entityManager): Response
{
if ($this->isCsrfTokenValid('delete' . $society->getId(), $request->request->get('_token'))) {
try {
$societyName = $society->getName();
$entityManager->remove($society);
$entityManager->flush();
$this->addFlash('success', 'La société "' . $societyName . '" a été supprimée avec succès.');
} catch (\Exception $e) {
$this->addFlash('error', 'Impossible de supprimer cette société : ' . $e->getMessage());
}
}
return $this->redirectToRoute('society_index', [], Response::HTTP_SEE_OTHER);
}
/**
* @Route("/upload/{id}", name="society_upload", methods={"POST"})
*/
public function uploadDocuments(Request $request, Society $society, FilesRepository $filesRepository)
{
if ($request->getMethod() == "POST") {
$entityManager = $this->getDoctrine()->getManager();
$file = $request->files->get('file');
$fileName = $file->getClientOriginalName();
$publicDirectory = $this->getParameter('file_directory');
$filepath = $publicDirectory . "/" . $society->getId();
if (!file_exists($filepath)) {
mkdir($filepath, 0777, true);
}
$fileName = $file->getClientOriginalName();
$file->move(
$filepath,
$fileName
);
$fileEntity = new Files();
$fileEntity->setName($fileName);
$fileEntity->setPath($society->getId() . '/' . $fileName);
$fileEntity->setSociety($society);
$entityManager->persist($fileEntity);
$entityManager->flush();
$html = $this->renderView('society/_document_ajax.html.twig', [
'files' => $filesRepository->findBy(['society' => $society->getId()]),
'directory' => "uploads/societyDocuments/",
'society' => $society
]);
return new Response($html);
}
}
/**
* @Route("/deleteFile/{id}/{file}", name="delete_file")
*/
public function refreshDocuments(Request $request, Society $society, $file, FilesRepository $filesRepository)
{
$entityManager = $this->getDoctrine()->getManager();
$fileEntity = $filesRepository->find($file);
$publicDirectory = $this->getParameter('file_directory');
$filepath = $publicDirectory . "/" . $fileEntity->getPath();
if (file_exists($filepath)) unlink($filepath);
$entityManager->remove($fileEntity);
$entityManager->flush();
$referer = $request->headers->get('referer');
return new RedirectResponse($referer);
}
/**
* @Route("/new/{id}", name="app_society_update_Clauses", methods={"GET", "POST"})
*/
public function updateStatus(Request $request, Society $society, EntityManagerInterface $entityManager): Response
{
if ($request->isXmlHttpRequest()) {
$data = $request->request->all();
$result = $selectedClauses = [];
$profilName = !empty($data['profil-name']) ? $data['profil-name'] : null;
$profilId = !empty($data['profil-id']) ? (int)$data['profil-id'] : null;
// Keys posted by the form that are NOT clause refs — must not pollute societyStatus
$nonClauseKeys = ['profil-name', 'profil-id', 'profile', '_token', 'save-mode'];
foreach ($data as $key => $value) {
if (in_array($key, $nonClauseKeys, true)) {
continue;
}
if (strpos($key, 'condition_') === 0) {
$result[$key] = $value;
} else {
$result[] = $key;
if ($profilName || $profilId) {
$clause = $entityManager->getRepository(ClauseScoiety::class)->findOneBy(['ref' => $key]);
if ($clause) {
$selectedClauses[] = $clause->getId();
}
}
}
}
if ($profilName && $selectedClauses) {
// Créer un nouveau profil
$selection = new UserClauseSelection();
$selection->setUser($this->getUser());
$selection->setSocietyType($society->getType());
$selection->setName($profilName);
$selection->setCreatedAt(new \DateTime());
$selection->setClauses($selectedClauses);
$entityManager->persist($selection);
$entityManager->flush();
$society->setSelectedProfil($selection);
$entityManager->flush();
} elseif ($profilId && $selectedClauses) {
// Mettre à jour un profil existant
$selection = $entityManager->getRepository(UserClauseSelection::class)->find($profilId);
if ($selection) {
$selection->setClauses($selectedClauses);
$entityManager->flush();
$society->setSelectedProfil($selection);
$entityManager->flush();
}
} else {
// Pas d'action profil : la sélection de la société ne correspond plus à un profil sauvegardé
$society->setSelectedProfil(null);
}
$society->setSocietyStatus($result);
$entityManager->persist($society);
$entityManager->flush();
return new Response('succes', Response::HTTP_OK);
}
return new Response('failed', Response::HTTP_BAD_REQUEST);
}
/**
* @Route("/preview-statuts/{id}", name="app_society_preview_statuts", methods={"POST"})
*/
public function previewStatuts(Request $request, Society $society, EntityManagerInterface $entityManager): Response
{
if (!$request->isXmlHttpRequest()) {
return new JsonResponse(['error' => 'Invalid request'], Response::HTTP_BAD_REQUEST);
}
$data = $request->request->all();
// Traitement des clauses similaire à updateStatus
$result = [];
foreach ($data as $key => $value) {
if (strpos($key, 'condition_') === 0) {
$result[$key] = $value;
} else {
$result[] = $key;
}
}
// Récupération des clauses/statuts pour le type de société
mb_internal_encoding("UTF-8");
$societyTypeName = $society->getType()->getName();
$decodedName = mb_convert_case($societyTypeName, MB_CASE_LOWER, "UTF-8");
$includeCommon = !str_contains($decodedName, 'sasu');
$allStatuts = $entityManager->getRepository(ClauseScoiety::class)->findBySocietyType($decodedName, $includeCommon);
$checkedRefs = array_values(array_filter(array_keys($result), 'is_int'));
$checkedRefs = array_map(function($k) use ($result) { return $result[$k]; }, $checkedRefs);
$statuts = array_values(array_filter($allStatuts, function($s) use ($checkedRefs) {
return in_array($s->getRef(), $checkedRefs);
}));
// Simulation de la mise à jour temporaire pour la preview
$conditionData = [];
foreach ($result as $key => $value) {
if (is_string($key) && strpos($key, 'condition_') === 0) {
$conditionData[$key] = $value;
}
}
$tempSociety = clone $society;
$tempSociety->setSocietyStatus($result);
// Override DB clauseConditionsArray with live form data so preview reflects current state
$tempSociety->setClauseConditionsArray($conditionData);
// Génération du PDF en mémoire
$pdfOptions = new \Dompdf\Options();
$pdfOptions->set('defaultFont', 'Arial');
$pdfOptions->set('isRemoteEnabled', true);
$dompdf = new \Dompdf\Dompdf($pdfOptions);
// Template du document statuts
$template = 'documentsPdf/statuts.html.twig';
if (!$this->container->get('twig')->getLoader()->exists($template)) {
return new JsonResponse(['error' => 'Template not found'], Response::HTTP_NOT_FOUND);
}
try {
$html = $this->container->get('twig')->render($template, [
'society' => $tempSociety,
'statuts' => $statuts,
'data' => $result
]);
$dompdf->loadHtml($html);
$dompdf->setPaper('A4', 'portrait');
$dompdf->render();
// Ajout du footer
$canvas = $dompdf->get_canvas();
$w = $canvas->get_width();
$h = $canvas->get_height();
// Résolution correcte du font via FontMetrics (évite arial.afm introuvable sur Linux)
$fontMetrics = $dompdf->getFontMetrics();
$footerFont = $fontMetrics->getFont('Helvetica', 'normal')
?? $fontMetrics->getFont('helvetica', 'normal')
?? $fontMetrics->getFont('Times', 'normal');
// Footer personnalisé ou par défaut
$customFooter = null;
if ($society->getClient() && $society->getClient()->getLogo()) {
$customFooter = $society->getClient()->getLogo()->getPiedDePage();
}
if ($customFooter) {
$canvas->page_text(30, $h - 30, $customFooter, $footerFont, 9, [0, 0, 0]);
} else {
$canvas->page_text(30, $h - 30, $society->getName(), $footerFont, 9, [0, 0, 0]);
$canvas->page_text($w / 2 - 60, $h - 30, "Généré le " . date('d/m/Y'), $footerFont, 9, [0, 0, 0]);
}
$canvas->page_text($w - 120, $h - 30, "Page {PAGE_NUM} de {PAGE_COUNT}", $footerFont, 9, [0, 0, 0]);
$canvas->line(30, $h - 45, $w - 30, $h - 45, [0.5, 0.5, 0.5], 0.5);
$output = $dompdf->output();
// Retourner le PDF en base64 pour affichage dans l'iframe
return new JsonResponse([
'success' => true,
'pdf' => base64_encode($output),
'mime' => 'data:application/pdf;base64,'
]);
} catch (\Exception $e) {
error_log('[previewStatuts] Exception: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine());
return new JsonResponse(['error' => 'Erreur: ' . $e->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* @Route("/sendMail/{id}", name="app_society_send_document", methods={"GET", "POST"})
*/
public function sendDocumentByMail(Request $request, int $id): Response
{
// Récupère le document en utilisant son ID
$documentSociety = $this->documentSocietyRepository->find($id);
if (!$documentSociety) {
throw $this->createNotFoundException('Le document n\'existe pas');
}
// Récupère la société associée au document
$society = $documentSociety->getSociety();
if ($request->isMethod('POST')) {
$recipients = $request->request->get('emails', []);
$content = $request->request->get('content', '');
// Récupère le chemin S3 du fichier PDF
$s3Key = $this->s3Service->extractKeyFromUrl($documentSociety->getPath());
// Utilise le service S3 pour récupérer le contenu du fichier
$fileContent = $this->s3Service->downloadDocument($s3Key);
if (!$fileContent) {
throw $this->createNotFoundException('Impossible de récupérer le fichier PDF depuis AWS S3');
}
// Convertit le flux en contenu lisible
if ($fileContent instanceof \Psr\Http\Message\StreamInterface) {
$fileContent = $fileContent->getContents();
}
// Détermine le nom du fichier
$filename = $documentSociety->getNameFormated() . '.pdf';
// Envoie un email avec le fichier PDF en pièce jointe à chaque destinataire
foreach ($recipients as $recipient) {
$this->mailService->sendEmailWithAttachment($recipient, 'MacsSolution.fr (Nouveau document)', $content, $fileContent, $filename);
}
$this->addFlash('success', 'Le mail a été envoyé avec succès.');
return $this->redirectToRoute('society_show', ['id' => $society->getId()]);
}
return $this->redirectToRoute('society_show', ['id' => $society->getId()]);
}
/**
* @Route("/society/update-signataires-by-document", name="app_society_update_signataires_document", methods={"POST"})
*/
public function updateSignataires(Request $request, EntityManagerInterface $entityManager, DocumentSocietyRepository $documentSocietyRepository): Response
{
if ($request->isXmlHttpRequest()) {
$data = $request->request->all();
$document = $documentSocietyRepository->find($data['documentId']);
$signataires = $data['selectedSignataires'];
$document->setSignataires($signataires);
$entityManager->persist($document);
$entityManager->flush();
return new Response('succes', Response::HTTP_OK);
}
return new Response('failed', Response::HTTP_BAD_REQUEST);
}
/**
* @Route("/society/sendMailMultiple/{id}", name="app_society_send_document_multiple", methods={"POST"})
*/
public function sendDocumentMultipleByMail(Request $request, Society $society, EntityManagerInterface $entityManager): Response
{
$action = $request->request->get('action');
$documentIds = $request->request->get('documents', []);
$documents = $this->documentSocietyRepository->findBy(['id' => $documentIds]);
if (empty($documents)) {
return $this->json(['error' => 'Aucun document sélectionné.'], 400);
}
// Crée un fichier ZIP temporaire
$zipName = sys_get_temp_dir() . '/' . uniqid() . '.zip';
$zip = new \ZipArchive();
$zip->open($zipName, \ZipArchive::CREATE);
$tempFiles = [];
foreach ($documents as $documentSociety) {
$key = $this->s3Service->extractKeyFromUrl($documentSociety->getPath());
$fileContent = $this->s3Service->downloadDocument($key);
if ($fileContent && strlen($fileContent) > 0) {
$tempFilePath = sys_get_temp_dir() . '/' . uniqid() . '.pdf';
file_put_contents($tempFilePath, $fileContent);
$tempFiles[] = $tempFilePath;
$zip->addFile($tempFilePath, ($documentSociety->getNameFormated() ?: $documentSociety->getName()) . '.pdf');
}
if ($action === 'download') {
$documentSociety->setIsDownload(true);
} elseif ($action === 'send') {
$documentSociety->setIsSend(true);
}
$entityManager->persist($documentSociety);
}
$zip->close();
$entityManager->flush();
// Nettoyer les fichiers temporaires PDF
foreach ($tempFiles as $tmp) {
@unlink($tmp);
}
if ($action === 'download') {
$response = new BinaryFileResponse($zipName);
$response->headers->set('Content-Type', 'application/zip');
$response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'documents.zip');
$response->deleteFileAfterSend(true);
return $response;
}
if ($action === 'send') {
// Destinataires choisis dans le modal
$emailsParam = $request->request->get('emails', []);
$recipients = array_unique(array_filter((array)$emailsParam));
if (empty($recipients)) {
@unlink($zipName);
return $this->json(['error' => 'Aucun destinataire sélectionné.'], 400);
}
$message = $request->request->get('message', 'Veuillez trouver en pièce jointe les documents de la société ' . $society->getName() . '.');
try {
$zipContent = file_get_contents($zipName);
foreach ($recipients as $recipient) {
$this->mailService->sendEmailWithAttachment(
$recipient,
'MacsSolution.fr – Documents société ' . $society->getName(),
$message,
$zipContent,
'documents.zip'
);
}
@unlink($zipName);
return $this->json(['success' => true, 'recipients' => count($recipients)]);
} catch (\Exception $e) {
@unlink($zipName);
return $this->json(['error' => 'Erreur lors de l\'envoi : ' . $e->getMessage()], 500);
}
}
return $this->json(['error' => 'Action invalide.'], 400);
}
/**
* @Route("/activity/reformulate", name="app_activity_reformulate")
*/
public function reformulateActivity(Request $request): JsonResponse
{
$data = json_decode($request->getContent(), true);
$text = $data['text'] ?? '';
try {
$response = $this->httpClient->request('POST', 'https://api.openai.com/v1/chat/completions', [
'headers' => [
'Authorization' => 'Bearer ' . ($_ENV['OPENAI_API_KEY'] ?? ''),
'Content-Type' => 'application/json',
],
'json' => [
'model' => 'gpt-4o',
'messages' => [
['role' => 'system', 'content' => 'Tu es un assistant juridique. Génère un objet social clair à partir de mots-clés. Réponds uniquement avec le texte de l\'objet social, sans titre, sans introduction, sans mise en forme Markdown, sans astérisques, sans gras. Le texte doit commencer directement par "La société a pour objet" ou par une liste numérotée.'],
['role' => 'user', 'content' => $text],
],
'temperature' => 0.7,
]
]);
$statusCode = $response->getStatusCode();
if ($statusCode === 401) {
return new JsonResponse(['error' => 'Clé API OpenAI invalide ou expirée. Veuillez la renouveler.'], 200);
}
$result = $response->toArray();
$reformulated = $result['choices'][0]['message']['content'] ?? '';
return new JsonResponse(['reformulated' => $reformulated]);
} catch (\Exception $e) {
$message = str_contains($e->getMessage(), '401')
? 'Clé API OpenAI invalide ou expirée. Veuillez la renouveler.'
: 'Erreur lors de la génération : ' . $e->getMessage();
return new JsonResponse(['error' => $message], 200);
}
}
/**
* @param EntityManagerInterface $entityManager
* @return null
*/
public function getFlush(EntityManagerInterface $entityManager)
{
return $entityManager->flush();
}
}