diff --git a/.changes/unreleased/Feature-20250219-120928.yaml b/.changes/unreleased/Feature-20250219-120928.yaml new file mode 100644 index 000000000..3c4f000e4 --- /dev/null +++ b/.changes/unreleased/Feature-20250219-120928.yaml @@ -0,0 +1,6 @@ +kind: Feature +body: Add a list of third parties from within the admin (csv download) +time: 2025-02-19T12:09:28.487991703+01:00 +custom: + Issue: "341" + SchemaChange: No schema change diff --git a/src/Bundle/ChillPersonBundle/config/services/controller.yaml b/src/Bundle/ChillPersonBundle/config/services/controller.yaml index d8374433f..b06d13f14 100644 --- a/src/Bundle/ChillPersonBundle/config/services/controller.yaml +++ b/src/Bundle/ChillPersonBundle/config/services/controller.yaml @@ -71,9 +71,8 @@ services: Chill\PersonBundle\Controller\PersonSignatureController: tags: [ 'controller.service_arguments' ] - Chill\PersonBundle\Controller\SocialIssueCSVExportController: - tags: [ 'controller.service_arguments' ] - Chill\PersonBundle\Controller\SocialActionCSVExportController: tags: [ 'controller.service_arguments' ] + Chill\PersonBundle\Controller\SocialIssueCSVExportController: + tags: [ 'controller.service_arguments' ] diff --git a/src/Bundle/ChillThirdPartyBundle/Controller/ThirdpartyCSVExportController.php b/src/Bundle/ChillThirdPartyBundle/Controller/ThirdpartyCSVExportController.php new file mode 100644 index 000000000..98d0ffafd --- /dev/null +++ b/src/Bundle/ChillThirdPartyBundle/Controller/ThirdpartyCSVExportController.php @@ -0,0 +1,104 @@ + 'csv'])] + public function socialIssueList(Request $request, string $_format = 'csv'): StreamedResponse + { + if (!$this->security->isGranted('ROLE_ADMIN')) { + throw new AccessDeniedHttpException('Only ROLE_ADMIN can export this list'); + } + + $streamCallback = function () use ($request) { + $output = fopen('php://output', 'w'); + + // Add BOM for UTF-8 + fwrite($output, "\xEF\xBB\xBF"); + + // Create CSV writer + $csv = Writer::createFromStream($output); + + // Write header row + $header = array_map( + fn (string $e) => $this->translator->trans($e), + [ + 'Id', + 'Kind', + 'Civility', + 'Firstname', + 'Name', + 'Profession', + 'Telephone', + 'Email', + 'Address', + 'Comment', + 'Parent', + 'Name_company', + 'Acronym', + 'Active', + 'Contact id', + 'Contact name', + 'Contact firstname', + 'Contact phone', + 'Contact email', + 'Contact address', + 'Contact profession', + ] + ); + $csv->insertOne($header); + + foreach ($this->thirdPartyRepository->findThirdpartiesWithContacts($request->getLocale()) as $row) { + $csv->insertOne($row); + flush(); + } + }; + + return new StreamedResponse( + function () use ($streamCallback) { + $streamCallback(); + }, + Response::HTTP_OK, + [ + 'Content-Encoding' => 'none', + 'Content-Type' => 'text/csv; charset=UTF-8', + 'Content-Disposition' => 'attachment; filename="thirdparties.csv"', + ] + ); + } +} diff --git a/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyRepository.php b/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyRepository.php index 7390808c8..d4e0e55ee 100644 --- a/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyRepository.php +++ b/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyRepository.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\ThirdPartyBundle\Repository; use Chill\ThirdPartyBundle\Entity\ThirdParty; +use Doctrine\DBAL\Connection; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Query; @@ -22,11 +23,63 @@ class ThirdPartyRepository implements ObjectRepository { private readonly EntityRepository $repository; - public function __construct(EntityManagerInterface $em) + public function __construct(EntityManagerInterface $em, private readonly Connection $connection) { $this->repository = $em->getRepository(ThirdParty::class); } + public function findThirdpartiesWithContacts(string $lang): iterable + { + $sql = " + SELECT + parent.id AS Id, + CASE + WHEN parent.kind = 'company' THEN 'Personne morale' + ELSE 'Personne physique' + END AS kind, + parent_civility.name->>? AS civility, + parent.firstname AS firstname, + parent.name AS name, + parent.profession AS profession, + parent.telephone AS telephone, + parent.email AS email, + CONCAT_WS(' ', parent_address.street, parent_address.streetnumber, parent_postal.code, parent_postal.label) AS address, + parent.comment AS comment, + parent.parent_id AS parent, + parent.name_company AS name_company, + parent.acronym AS acronym, + CASE WHEN parent.active THEN 'X' ELSE '' END AS active, + + -- Contact (child third-party) details + contact.id AS contact_id, + contact.name AS contact_name, + contact.firstname AS contact_firstname, + contact.telephone AS contact_phone, + contact.email AS contact_email, + contact.profession AS contact_profession, + CONCAT_WS(' ', contact_address.street, contact_address.streetnumber, contact_postal.code, contact_postal.label) AS contact_address + + FROM chill_3party.third_party parent + LEFT JOIN chill_main_civility parent_civility ON parent.civility_id = parent_civility.id + LEFT JOIN chill_main_address parent_address ON parent.address_id = parent_address.id + LEFT JOIN chill_main_postal_code parent_postal ON parent_address.postcode_id = parent_postal.id + + -- Join contacts (child third parties) + LEFT JOIN chill_3party.third_party contact ON contact.parent_id = parent.id + LEFT JOIN chill_main_address contact_address ON contact.address_id = contact_address.id + LEFT JOIN chill_main_postal_code contact_postal ON contact_address.postcode_id = contact_postal.id + + -- Exclude contacts from appearing as standalone rows + WHERE parent.parent_id IS NULL + + -- Sorting order + ORDER BY parent.name ASC, parent.name_company ASC, parent.id ASC, + contact.name ASC, contact.firstname ASC, contact.id ASC + "; + + return $this->connection->iterateAssociative($sql, [$lang]); + } + /** * count amongst parties associated to $centers, with $terms parameters. */ diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/views/Admin/index.html.twig b/src/Bundle/ChillThirdPartyBundle/Resources/views/Admin/index.html.twig index 60f39ddeb..f50a1d90a 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/views/Admin/index.html.twig +++ b/src/Bundle/ChillThirdPartyBundle/Resources/views/Admin/index.html.twig @@ -9,5 +9,9 @@ {% block layout_wvm_content %} {% block admin_content %}

{{ 'Third party configuration' |trans }}

+
+

{{ 'admin.export_description'|trans }}

+ +
{% endblock %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml b/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml index b6a568249..52bae6fee 100644 --- a/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml @@ -133,3 +133,22 @@ is thirdparty: Le demandeur est un tiers Filter by person's who have a residential address located at a thirdparty of type: Filtrer les usagers qui ont une addresse de résidence chez un tiers "Filtered by person's who have a residential address located at a thirdparty of type %thirdparty_type% and valid on %date_calc%": "Uniquement les usagers qui ont une addresse de résidence chez un tiers de catégorie %thirdparty_type% et valide sur la date %date_calc%" + + +# admin +admin: + export_description: Liste des tiers (format CSV) + +Profession: Profession +Firstname: Prénom +Name_company: Service/Département +Address: Adresse +Civility: Civilité +Id: Identifiant +Contact id: Identifiant du contact +Contact name: Nom du contact +Contact firstname: Prénom du contact +Contact phone: Téléphone du contact +Contact email: Courrier électronique du contact +Contact address: Adresse du contact +Contact profession: Profession du contact