diff --git a/app/config/services.yml b/app/config/services.yml
index fd465e8b1..b0cc9f3a4 100644
--- a/app/config/services.yml
+++ b/app/config/services.yml
@@ -23,6 +23,9 @@ services:
CCMBenchmark\Ting\Repository\Repository:
tags: [ 'app.vendor.ting_repository' ]
+ AppBundle\Accounting\Invoices\Generator\InvoiceGeneration:
+ tags: [ 'app.invoice_generation_handler' ]
+
Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler:
public: false
arguments:
@@ -167,8 +170,12 @@ services:
Afup\Site\Forum\Inscriptions:
autowire: true
- Afup\Site\Association\CotisationsFactory:
- autowire: true
+ AppBundle\Accounting\Invoices\Generator\MemberInvoiceGenerator: ~
+ AppBundle\Accounting\Invoices\Generator\CompanyMemberInvoiceGenerator: ~
+
+ AppBundle\Accounting\Invoices\InvoiceGenerator:
+ arguments:
+ $handlers: !tagged_iterator app.invoice_generation_handler
Afup\Site\Corporate\Page:
autowire: true
@@ -176,9 +183,6 @@ services:
Afup\Site\Forum\Facturation:
autowire: true
- Afup\Site\Association\Cotisations:
- factory: ['@Afup\Site\Association\CotisationsFactory', 'create']
-
Afup\Site\Comptabilite\Comptabilite:
autowire: true
diff --git a/sources/Afup/Association/Cotisations.php b/sources/Afup/Association/Cotisations.php
deleted file mode 100644
index fa2dfa576..000000000
--- a/sources/Afup/Association/Cotisations.php
+++ /dev/null
@@ -1,705 +0,0 @@
-value;
- $requete .= ' AND id_personne=' . $id_personne . ' ';
- $requete .= 'ORDER BY ' . $ordre;
- if ($associatif) {
- return $this->_bdd->obtenirAssociatif($requete);
- } else {
- return $this->_bdd->obtenirTous($requete);
- }
- }
-
- /**
- * Renvoit la cotisation demandée
- *
- * @param int $id Identifiant de la cotisation
- * @param string $champs Champs à renvoyer
- * @return array
- */
- public function obtenir($id, string $champs = '*')
- {
- $requete = 'SELECT';
- $requete .= ' ' . $champs . ' ';
- $requete .= 'FROM';
- $requete .= ' afup_cotisations ';
- $requete .= 'WHERE';
- $requete .= ' id=' . $id;
- return $this->_bdd->obtenirEnregistrement($requete);
- }
-
- /**
- * Renvoit le numéro de la prochaine facture au format : {année}-{index depuis le début de l'année}
- *
- */
- public function _genererNumeroFacture(): string
- {
- $requete = 'SELECT';
- $requete .= " MAX(CAST(SUBSTRING_INDEX(numero_facture, '-', -1) AS UNSIGNED)) + 1 ";
- $requete .= 'FROM';
- $requete .= ' afup_cotisations ';
- $requete .= 'WHERE';
- $requete .= ' LEFT(numero_facture, 4)=' . $this->_bdd->echapper(date('Y'));
- $requete .= ' OR LEFT(numero_facture, 10)=' . $this->_bdd->echapper("COTIS-" . date('Y'));
- $index = $this->_bdd->obtenirUn($requete);
- return 'COTIS-' . date('Y') . '-' . (is_null($index) ? 1 : $index);
- }
-
- /**
- * Ajoute une cotisation
- *
- * @param int $id_personne Identifiant de la personne
- * @param float $montant Adresse de la personne
- * @param int $type_reglement Type de règlement (espèces, chèque, virement)
- * @param string $informations_reglement Informations concernant le règlement (numéro de chèque, de virement etc.)
- * @param int $date_debut Date de début de la
- * cotisation
- * @param int $date_fin Date de fin de la cotisation
- * @param string $commentaires Commentaires concernnant la cotisation
- * @param string $referenceClient Reference client à mentionner sur la facture
- * @return bool Succès de l'ajout
- */
- public function ajouter(MemberType $type_personne, $id_personne, $montant, $type_reglement,
- $informations_reglement, $date_debut, $date_fin, $commentaires, $referenceClient = null): bool
- {
- $requete = 'INSERT INTO ';
- $requete .= ' afup_cotisations (type_personne, id_personne, montant, type_reglement , informations_reglement,';
- $requete .= ' date_debut, date_fin, numero_facture, token, commentaires, reference_client, date_facture) ';
- $requete .= 'VALUES (';
- $requete .= $type_personne->value . ',';
- $requete .= $id_personne . ',';
- $requete .= $montant . ',';
- $requete .= $this->_bdd->echapper($type_reglement) . ',';
- $requete .= $this->_bdd->echapper($informations_reglement) . ',';
- $requete .= $date_debut . ',';
- $requete .= $date_fin . ',';
- $requete .= $this->_bdd->echapper($this->_genererNumeroFacture()) . ',';
- $requete .= $this->_bdd->echapper(base64_encode(random_bytes(30))) . ',';
- $requete .= $this->_bdd->echapper($commentaires) . ',';
- $requete .= $this->_bdd->echapper($referenceClient) . ',';
- $requete .= $this->_bdd->echapper(date('Y-m-d\TH:i:s')) . ')';
- return $this->_bdd->executer($requete) !== false;
- }
-
- /**
- * Modifie une cotisation
- *
- * @param int $id Identifiant de la cotisation à modifier
- * @param int $type_personne Type de la personne (morale ou physique)
- * @param int $id_personne Identifiant de la personne
- * @param float $montant Adresse de la personne
- * @param int $type_reglement Type de règlement (espèces, chèque, virement)
- * @param string $informations_reglement Informations concernant le règlement (numéro de chèque, de virement etc.)
- * @param int $date_debut Date de début de la
- * cotisation
- * @param int $date_fin Date de fin de la cotisation
- * @param string $commentaires Commentaires concernnant la cotisation
- * @param string $referenceClient Reference client à mentionner sur la facture
- * @return bool Succès de la modification
- */
- public function modifier($id, $type_personne, $id_personne, $montant, $type_reglement,
- $informations_reglement, $date_debut, $date_fin, $commentaires, $referenceClient): bool
- {
- $requete = 'UPDATE';
- $requete .= ' afup_cotisations ';
- $requete .= 'SET';
- $requete .= ' type_personne=' . $type_personne . ',';
- $requete .= ' id_personne=' . $id_personne . ',';
- $requete .= ' montant=' . $montant . ',';
- $requete .= ' type_reglement=' . $type_reglement . ',';
- $requete .= ' informations_reglement=' . $this->_bdd->echapper($informations_reglement) . ',';
- $requete .= ' date_debut=' . $date_debut . ',';
- $requete .= ' date_fin=' . $date_fin . ',';
- $requete .= ' commentaires=' . $this->_bdd->echapper($commentaires) . ',';
- $requete .= ' reference_client=' . $this->_bdd->echapper($referenceClient) . ' ';
- $requete .= 'WHERE';
- $requete .= ' id=' . $id;
- return $this->_bdd->executer($requete) !== false;
- }
-
- public function estDejaReglee($cmd)
- {
- $requete = 'SELECT';
- $requete .= ' 1 ';
- $requete .= 'FROM';
- $requete .= ' afup_cotisations ';
- $requete .= 'WHERE';
- $requete .= ' informations_reglement=' . $this->_bdd->echapper($cmd);
- return $this->_bdd->obtenirUn($requete);
- }
-
- public function notifierReglementEnLigneAuTresorier(string $cmd, float $total, string $autorisation, string $transaction, UserRepository $userRepository): ?bool
- {
- if (str_starts_with($cmd, 'F')) {
- // Facture
- $invoiceNumber = substr($cmd, 1);
- $cotisation = $this->getByInvoice($invoiceNumber);
- $type_personne = $cotisation['type_personne'];
- $id_personne = $cotisation['id_personne'];
-
- } else {
- // Cotisation
- [$ref, $date, $type_personne, $id_personne, $reste] = explode('-', $cmd, 5);
- }
-
- $infos = [
- 'id' => $id_personne,
- 'type' => $type_personne,
- 'nom' => 'N.C.',
- 'prenom' => 'N.C.',
- 'email' => 'N.C.',
- ];
-
- if ($type_personne == MemberType::MemberCompany->value) {
- if ($company = $this->companyMemberRepository?->get($id_personne)) {
- $infos['nom'] = $company->getLastName();
- $infos['prenom'] = $company->getFirstName();
- $infos['email'] = $company->getEmail();
- }
- } else {
- if ($user = $userRepository->get($id_personne)) {
- $infos['nom'] = $user->getLastName();
- $infos['prenom'] = $user->getFirstName();
- $infos['email'] = $user->getEmail();
- }
- }
-
-
- $sujet = "Paiement cotisation AFUP\n";
-
- $corps = "Bonjour, \n\n";
- $corps .= "Une cotisation annuelle AFUP a été réglée.\n\n";
- $corps .= "Personne : " . $infos['nom'] . " " . $infos['prenom'] . " (" . $infos['email'] . ")\n";
- $corps .= "URL : /admin/accounting/membership-fee/list/" . $infos['type'] . "/" . $infos['id'] . "\n";
- $corps .= "Commande : " . $cmd . "\n";
- $corps .= "Total : " . $total . "\n";
- $corps .= "Autorisation : " . $autorisation . "\n";
- $corps .= "Transaction : " . $transaction . "\n\n";
-
- $ok = Mailing::envoyerMail(new Message($sujet, new MailUser(MailUser::DEFAULT_SENDER_EMAIL, MailUser::DEFAULT_SENDER_NAME), MailUserFactory::tresorier()), $corps);
-
- if (false === $ok) {
- return false;
- }
- return null;
- }
-
- public function validerReglementEnLigne($cmd, $total, string $autorisation, string $transaction)
- {
- $reference = substr((string) $cmd, 0, strlen((string) $cmd) - 4);
- $verif = substr((string) $cmd, strlen((string) $cmd) - 3, strlen((string) $cmd));
- $result = false;
-
- if (str_starts_with((string) $cmd, 'F')) {
- // This is an invoice ==> we dont have to create a new cotisation, just update the existing one
- $invoiceNumber = substr((string) $cmd, 1);
- $cotisation = $this->getByInvoice($invoiceNumber);
-
- $this
- ->updatePayment(
- $cotisation['id'],
- AFUP_COTISATIONS_REGLEMENT_ENLIGNE, "autorisation : " . $autorisation . " / transaction : " . $transaction,
- );
- } elseif (substr(md5($reference), -3) === strtolower($verif) && !$this->estDejaReglee($cmd)) {
- [$ref, $date, $type_personne, $id_personne, $reste] = explode('-', (string) $cmd, 5);
- $date_debut = mktime(0, 0, 0, (int) substr($date, 2, 2), (int) substr($date, 0, 2), (int) substr($date, 4, 4));
-
- $cotisation = $this->obtenirDerniere(MemberType::from((int) $type_personne), $id_personne);
- $date_fin_precedente = $cotisation === false ? 0 : $cotisation['date_fin'];
-
- if ($date_fin_precedente > 0) {
- $date_debut = strtotime('+1day', (int) $date_fin_precedente);
- }
-
- $date_fin = $this->finProchaineCotisation($cotisation)->format('U');
- $result = $this->ajouter(
- MemberType::from((int) $type_personne),
- $id_personne,
- $total,
- AFUP_COTISATIONS_REGLEMENT_ENLIGNE,
- $cmd,
- $date_debut,
- $date_fin,
- "autorisation : " . $autorisation . " / transaction : " . $transaction);
- }
-
- return $result;
- }
-
- /**
- * @return array{type: int, id: int}
- */
- public function getAccountFromCmd(string $cmd): array
- {
- $arr = explode('-', $cmd, 5);
- // Depuis une facture : $cmd=FCOTIS-2023-202
- if (3 === count($arr)) {
- return ['type' => MemberType::MemberCompany->value, 'id' => (int) $arr[2]];
- }
-
- // Depuis une cotisation : $cmd=C2023-211120232237-0-5-PAUL-431
- [$ref, $date, $memberType, $memberId, $stuff] = $arr;
-
- return ['type' => (int) $memberType, 'id' => (int) $memberId];
- }
-
- /**
- * Supprime une cotisation
- *
- * @param int $id Identifiant de la cotisation à supprimer
- * @return bool Succès de la suppression
- */
- public function supprimer($id)
- {
- $requete = 'DELETE FROM afup_cotisations WHERE id=' . $id;
- return $this->_bdd->executer($requete);
- }
-
- /**
- * Génère une facture au format PDF
- *
- * @param int $id_cotisation Identifiant de la cotisation
- * @param string $chemin Chemin du fichier PDF à générer. Si ce chemin est omi, le PDF est renvoyé au navigateur.
- * @return int Le numero de la facture
- */
- public function genererFacture($id_cotisation, $chemin = null)
- {
- $requete = 'SELECT * FROM afup_cotisations WHERE id=' . $id_cotisation;
- $cotisation = $this->_bdd->obtenirEnregistrement($requete);
-
- $table = $cotisation['type_personne'] == MemberType::MemberCompany->value ? 'afup_personnes_morales' : 'afup_personnes_physiques';
- $requete = 'SELECT * FROM ' . $table . ' WHERE id=' . $cotisation['id_personne'];
- $personne = $this->_bdd->obtenirEnregistrement($requete);
-
- if ($cotisation['date_facture'] !== null) {
- $dateFacture = \DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $cotisation['date_facture']);
- } else {
- $dateFacture = \DateTimeImmutable::createFromFormat('U', $cotisation['date_debut']);
- }
-
- $bankAccountFactory = new BankAccountFactory();
- $isSubjectedToVat = Vat::isSubjectedToVat($dateFacture);
- // Construction du PDF
- $pdf = new PDF_Facture($bankAccountFactory->createApplyableAt($dateFacture), $isSubjectedToVat);
- $pdf->AddPage();
-
- $pdf->Cell(130, 5);
- $pdf->Cell(60, 5, 'Le ' . $dateFacture->format('d/m/Y'));
-
- $pdf->Ln();
- $pdf->Ln();
- $pdf->Ln();
-
- // A l'attention du client [adresse]
- $pdf->SetFont('Arial', 'BU', 10);
- $pdf->Cell(130, 5, 'Objet : Facture n°' . $cotisation['numero_facture']);
- $pdf->SetFont('Arial', '', 10);
-
- if ($cotisation['type_personne'] == MemberType::MemberCompany->value) {
- $nom = $personne['raison_sociale'];
- $patternPrefix = $personne['raison_sociale'];
- } else {
- $nom = $personne['prenom'] . ' ' . $personne['nom'];
- $patternPrefix = $personne['nom'];
- }
- $pdf->Ln(10);
- $pdf->MultiCell(130, 5, $nom . "\n" . $personne['adresse'] . "\n" . $personne['code_postal'] . "\n" . $personne['ville']);
-
- if (isset($cotisation['reference_client'])) {
- $pdf->Ln(10);
- $pdf->MultiCell(180, 5, sprintf(
- "Référence client : %s",
- $cotisation['reference_client'],
- ));
- }
-
- $pdf->Ln(15);
-
- $pdf->MultiCell(180, 5, "Facture concernant votre adhésion à l'Association Française des Utilisateurs de PHP (AFUP).");
-
- if (false === $isSubjectedToVat) {
- // Cadre
- $pdf->Ln(10);
- $pdf->SetFillColor(200, 200, 200);
- $pdf->Cell(50, 5, 'Code', 1, 0, 'L', 1);
- $pdf->Cell(100, 5, 'Désignation', 1, 0, 'L', 1);
- $pdf->Cell(40, 5, 'Prix', 1, 0, 'L', 1);
-
- $pdf->Ln();
- $pdf->SetFillColor(255, 255, 255);
- $pdf->Cell(50, 5, 'ADH', 1);
- $pdf->Cell(100, 5, "Adhésion AFUP jusqu'au " . date('d/m/Y', (int) $cotisation['date_fin']), 1);
- $pdf->Cell(40, 5, $cotisation['montant'] . ' €', 1);
-
- $pdf->Ln(15);
- $pdf->Cell(10, 5, 'TVA non applicable - art. 293B du CGI');
- } else {
- // On stocke le montant de la cotisation TTC, pour les personnes physiques c'est le même, par contre pour les personnes morales
- // ce n'est pas le même, afin d'éviter d'appliquer deux fois la TVA, on applique ce hotfix
- if ($cotisation['type_personne'] == MemberType::MemberCompany->value) {
- $cotisation['montant'] = Vat::getRoundedWithoutVatPriceFromPriceWithVat($cotisation['montant'], Utils::MEMBERSHIP_FEE_VAT_RATE);
- }
-
-
- // Cadre
- $pdf->Ln(10);
- $pdf->SetFillColor(200, 200, 200);
- $pdf->Cell(20, 5, 'Code', 1, 0, 'L', 1);
- $pdf->Cell(95, 5, 'Désignation', 1, 0, 'L', 1);
- $pdf->Cell(25, 5, 'Prix HT', 1, 0, 'R', 1);
- $pdf->Cell(25, 5, 'Taux TVA', 1, 0, 'R', 1);
- $pdf->Cell(25, 5, 'Prix TTC', 1, 0, 'R', 1);
-
- if ($cotisation['type_personne'] == MemberType::MemberCompany->value) {
- [$totalHt, $total] = $this->buildDetailsPersonneMorale($pdf, $cotisation['montant'], $cotisation['date_fin']);
- } else {
- [$totalHt, $total] = $this->buildDetailsPersonnePhysique($pdf, $cotisation['montant'], $cotisation['date_fin']);
- }
-
- $pdf->Ln();
- $pdf->SetFillColor(225, 225, 225);
- $pdf->Cell(165, 5, 'Total HT', 1, 0, 'R', 1);
- $pdf->Cell(25, 5, $this->formatFactureValue($totalHt) . ' €', 1, 0, 'R', 1);
-
- $pdf->Ln();
- $pdf->SetFillColor(255, 255, 255);
- $pdf->Cell(165, 5, 'Total TVA 20%', 1, 0, 'R', 1);
- $pdf->Cell(25, 5, $this->formatFactureValue($total - $totalHt) . ' €', 1, 0, 'R', 1);
-
- $pdf->Ln();
- $pdf->SetFillColor(225, 225, 225);
- $pdf->Cell(165, 5, 'Total TTC', 1, 0, 'R', 1);
- $pdf->Cell(25, 5, $this->formatFactureValue($total) . ' €', 1, 0, 'R', 1);
- }
-
- $pdf->Ln(15);
- $pdf->Cell(10, 5, 'Lors de votre règlement, merci de préciser la mention : "Facture n°' . $cotisation['numero_facture'] . '"');
-
- if (is_null($chemin)) {
- $pattern = str_replace(' ', '', $patternPrefix) . '_' . $cotisation['numero_facture'] . '_' . date('dmY', (int) $cotisation['date_debut']) . '.pdf';
-
- $pdf->Output($pattern, 'D', true);
- } else {
- $pdf->Output($chemin, 'F', true);
- }
-
- return $cotisation['numero_facture'];
- }
-
- private function buildDetailsPersonneMorale(PDF_Facture $pdf, $montant, $dateFin): array
- {
- $montantTtc = $montant * (1 + Utils::MEMBERSHIP_FEE_VAT_RATE);
- $pdf->Ln();
- $pdf->SetFillColor(255, 255, 255);
- $pdf->Cell(20, 5, 'ADH', 1);
- $pdf->Cell(95, 5, "Adhésion AFUP jusqu'au " . date('d/m/Y', (int) $dateFin), 1);
- $pdf->Cell(25, 5, $this->formatFactureValue($montant) . ' €', 1, 0, 'R');
- $pdf->Cell(25, 5, (Utils::MEMBERSHIP_FEE_VAT_RATE * 100 . ' %'), 1, 0, 'R');
- $pdf->Cell(25, 5, $this->formatFactureValue($montantTtc) . ' €', 1, 0, 'R');
- $totalHt = $montant;
- $total = $montantTtc;
-
- return [$totalHt, $total];
- }
-
- private function buildDetailsPersonnePhysique(PDF_Facture $pdf, $montant, $dateFin): array
- {
- $montantFixeHt = 5 / 100 * $montant;
- $montantFixeTTc = $montantFixeHt * (1 + Utils::MEMBERSHIP_FEE_VAT_RATE);
- $montantVariable = $montant - $montantFixeTTc;
-
- $pdf->Ln();
- $pdf->SetFillColor(255, 255, 255);
- $pdf->Cell(20, 5, 'ADH-var', 1);
- $pdf->Cell(95, 5, "Adhésion AFUP jusqu'au " . date('d/m/Y', (int) $dateFin) . ' - part variable', 1);
- $pdf->Cell(25, 5, $this->formatFactureValue($montantFixeHt) . ' €', 1, 0, 'R');
- $pdf->Cell(25, 5, (Utils::MEMBERSHIP_FEE_VAT_RATE * 100 . ' %'), 1, 0, 'R');
- $pdf->Cell(25, 5, $this->formatFactureValue($montantFixeTTc) . ' €', 1, 0, 'R');
-
- $pdf->Ln();
- $pdf->SetFillColor(255, 255, 255);
- $pdf->Cell(20, 5, 'ADH-fixe', 1);
- $pdf->Cell(95, 5, "Adhésion AFUP jusqu'au " . date('d/m/Y', (int) $dateFin) . ' - part fixe', 1);
- $pdf->Cell(25, 5, $this->formatFactureValue($montantVariable) . ' €', 1, 0, 'R');
- $pdf->Cell(25, 5, '0 %', 1, 0, 'R');
- $pdf->Cell(25, 5, $this->formatFactureValue($montantVariable) . ' €', 1, 0, 'R');
-
- $totalHt = $montantFixeHt + $montantVariable;
- $total = $montantFixeTTc + $montantVariable;
-
- return [$totalHt, $total];
- }
-
- private function formatFactureValue($value): string
- {
- return number_format($value, 2, ',', ' ');
- }
-
- /**
- * Envoi par mail d'une facture au format PDF
- *
- * @param int $id_cotisation Identifiant de la cotisation
- * @return bool Succès de l'envoi
- */
- public function envoyerFacture($id_cotisation, Mailer $mailer, UserRepository $userRepository)
- {
- $personne = $this->obtenir($id_cotisation, 'type_personne, id_personne');
-
- if ($personne['type_personne'] == MemberType::MemberCompany->value) {
- $company = $this->companyMemberRepository ? $this->companyMemberRepository->get($personne['id_personne']) : null;
- Assert::notNull($company);
- $contactPhysique = [
- 'nom' => $company->getLastName(),
- 'prenom' => $company->getFirstName(),
- 'email' => $company->getEmail(),
- ];
- } else {
- $user = $userRepository->get($personne['id_personne']);
- Assert::notNull($user);
- $contactPhysique = [
- 'nom' => $user->getLastName(),
- 'prenom' => $user->getFirstName(),
- 'email' => $user->getEmail(),
- ];
- }
- $patternPrefix = $contactPhysique['nom'];
-
- $corps = "Bonjour,
";
- $corps .= "
Veuillez trouver ci-joint la facture correspondant à votre adhésion à l'AFUP.
";
- $corps .= "Nous restons à votre disposition pour toute demande complémentaire.
";
- $corps .= "Le bureau
";
- $corps .= AFUP_RAISON_SOCIALE . "
";
- $corps .= AFUP_ADRESSE . "
";
- $corps .= AFUP_CODE_POSTAL . " " . AFUP_VILLE . "
";
-
- $cheminFacture = AFUP_CHEMIN_RACINE . 'cache/fact' . $id_cotisation . '.pdf';
- $numeroFacture = $this->genererFacture($id_cotisation, $cheminFacture);
- $cotisation = $this->obtenirDerniere(MemberType::from((int) $personne['type_personne']), $personne['id_personne']);
- $pattern = str_replace(' ', '', $patternPrefix) . '_' . $numeroFacture . '_' . date('dmY', (int) $cotisation['date_debut']) . '.pdf';
-
- $message = new Message('Facture AFUP', null, new MailUser(
- $contactPhysique['email'],
- sprintf('%s %s', $contactPhysique['prenom'], $contactPhysique['nom']),
- ));
- $message->addAttachment(new Attachment(
- $cheminFacture,
- $pattern,
- 'base64',
- 'application/pdf',
- ));
- $ok = $mailer->sendTransactional($message, $corps);
- @unlink($cheminFacture);
-
- return $ok;
- }
-
- /**
- * Retourne la dernière cotisation d'une personne morale
- * @param int $id_personne Identifiant de la personne
- * @return array|false
- */
- public function obtenirDerniere(MemberType $type_personne, $id_personne)
- {
- $requete = 'SELECT';
- $requete .= ' * ';
- $requete .= 'FROM';
- $requete .= ' afup_cotisations ';
- $requete .= 'WHERE';
- $requete .= ' type_personne=' . $type_personne->value . ' ';
- $requete .= ' AND id_personne=' . $id_personne . ' ';
- $requete .= 'ORDER BY';
- $requete .= ' date_fin DESC ';
- $requete .= 'LIMIT 0, 1 ';
- return $this->_bdd->obtenirEnregistrement($requete);
- }
-
- /**
- * Retourne la date de début d'une cotisation.
- *
- * Cette date est déterminée par la date de fin de la cotisation précédente
- * s'il y en a une ou alors sur la date du jour dans le cas contraire.
- *
- * @param int $type_personne Identifiant du type de personne
- * @param int $id_personne Identifiant de la personne
- * @return int Timestamp de la date de la cotisation
- */
- public function obtenirDateDebut($type_personne, $id_personne)
- {
- $requete = 'SELECT';
- $requete .= ' date_fin ';
- $requete .= 'FROM';
- $requete .= ' afup_cotisations ';
- $requete .= 'WHERE';
- $requete .= ' type_personne=' . $type_personne . ' ';
- $requete .= 'AND';
- $requete .= ' id_personne=' . $id_personne . ' ';
- $requete .= 'ORDER BY';
- $requete .= ' date_fin DESC';
- $date_debut = $this->_bdd->obtenirUn($requete);
-
- if ($date_debut !== false) {
- return (int) $date_debut;
- } else {
- return time();
- }
- }
-
- /**
- * @param array|false $cotisation from Afup_Personnes_Physiques::obtenirDerniereCotisation
- * @return DateTime Date of end of next subscription
- */
- public function finProchaineCotisation($cotisation = false): DateTime
- {
- $endSubscription = $cotisation === false ? new DateTime() : new \DateTime('@' . $cotisation['date_fin']);
- $base = $now = new DateTime();
-
- $year = new DateInterval('P1Y');
-
- if ($endSubscription > $now) {
- $base = $endSubscription;
- }
-
- $base->add($year);
- return $base;
- }
-
- /**
- * Renvoit la cotisation demandée
- *
- * @param string $invoiceId Identifiant de la facture
- * @param string|null $token Token de la facture. Si null, pas de vérification
- * @return array
- */
- public function getByInvoice(string $invoiceId, string $token = null)
- {
- $requete = 'SELECT';
- $requete .= ' * ';
- $requete .= 'FROM';
- $requete .= ' afup_cotisations ';
- $requete .= 'WHERE';
- $requete .= ' numero_facture = ' . $this->_bdd->echapper($invoiceId);
- if ($token !== null) {
- $requete .= ' AND token = ' . $this->_bdd->echapper($token);
- }
-
- return $this->_bdd->obtenirEnregistrement($requete);
- }
-
- /**
- * Modifie une cotisation
- *
- * @param int $id Identifiant de la cotisation à modifier
- * @param int $type_reglement Type de règlement (espèces, chèque, virement)
- * @param string $informations_reglement Informations concernant le règlement (numéro de chèque, de virement etc.)
- * @return bool Succès de la modification
- */
- public function updatePayment($id, $type_reglement, string $informations_reglement): bool
- {
- $requete = 'UPDATE';
- $requete .= ' afup_cotisations ';
- $requete .= 'SET';
- $requete .= ' type_reglement=' . $type_reglement . ',';
- $requete .= ' informations_reglement=' . $this->_bdd->echapper($informations_reglement);
- $requete .= ' WHERE';
- $requete .= ' id=' . $id;
- return $this->_bdd->executer($requete) !== false;
- }
-
- public function isCurrentUserAllowedToReadInvoice(string $invoiceId)
- {
- if (!$this->_droits) {
- throw new \RuntimeException('La variable $_droits ne doit pas être null.');
- }
-
- $sql = 'SELECT type_personne, id_personne FROM afup_cotisations WHERE id = ' . $this->_bdd->echapper($invoiceId);
- $result = $this->_bdd->obtenirEnregistrement($sql);
-
- if (!$result) {
- return false;
- }
-
- /**
- * si type_personne = 0, alors personne physique: id_personne doit être identique l'id de l'utilisateur connecté
- */
- if ($result['type_personne'] === "0") {
- return $result['id_personne'] == $this->_droits->obtenirIdentifiant();
- }
-
- /**
- * si type_personne = 1, alors personne morale: id_personne doit être égale à compagnyId de l'utilisateur connecté
- * qui doit aussi avoir le droit "ROLE_COMPAGNY_MANAGER"
- */
- if ($result['type_personne'] == MemberType::MemberCompany->value) {
- return $this->_droits->verifierDroitManagerPersonneMorale($result['id_personne']);
- }
-
- return false;
- }
-
- public function setCompanyMemberRepository(CompanyMemberRepository $companyMemberRepository): void
- {
- $this->companyMemberRepository = $companyMemberRepository;
- }
-}
diff --git a/sources/Afup/Association/CotisationsFactory.php b/sources/Afup/Association/CotisationsFactory.php
deleted file mode 100644
index 804b0cb18..000000000
--- a/sources/Afup/Association/CotisationsFactory.php
+++ /dev/null
@@ -1,31 +0,0 @@
-tokenStorage, $this->authorizationChecker),
- );
-
- $cotisations->setCompanyMemberRepository($this->companyMemberRepository);
-
- return $cotisations;
- }
-}
diff --git a/sources/AppBundle/Accounting/Invoices/Dto/InvoiceData.php b/sources/AppBundle/Accounting/Invoices/Dto/InvoiceData.php
new file mode 100644
index 000000000..ed3517643
--- /dev/null
+++ b/sources/AppBundle/Accounting/Invoices/Dto/InvoiceData.php
@@ -0,0 +1,16 @@
+getCompanyName(),
+ $member->getAddress(),
+ $member->getZipcode(),
+ $member->getCity(),
+ $member->getCompanyName(),
+ );
+ }
+
+ public function support(User|CompanyMember $user): bool
+ {
+ return $user instanceof CompanyMember;
+ }
+}
diff --git a/sources/AppBundle/Accounting/Invoices/Generator/InvoiceGeneration.php b/sources/AppBundle/Accounting/Invoices/Generator/InvoiceGeneration.php
new file mode 100644
index 000000000..e9a0f0ca5
--- /dev/null
+++ b/sources/AppBundle/Accounting/Invoices/Generator/InvoiceGeneration.php
@@ -0,0 +1,16 @@
+getFirstName() . ' ' . $member->getLastName(),
+ $member->getAddress(),
+ $member->getZipcode(),
+ $member->getCity(),
+ $member->getLastName(),
+ );
+ }
+
+ public function support(User|CompanyMember $user): bool
+ {
+ return $user instanceof User;
+ }
+}
diff --git a/sources/AppBundle/Accounting/Invoices/InvoiceGenerator.php b/sources/AppBundle/Accounting/Invoices/InvoiceGenerator.php
new file mode 100644
index 000000000..d13d024de
--- /dev/null
+++ b/sources/AppBundle/Accounting/Invoices/InvoiceGenerator.php
@@ -0,0 +1,32 @@
+ $handlers
+ */
+ public function __construct(private readonly iterable $handlers) {}
+
+ public function getInvoiceData(User|CompanyMember $user): InvoiceData
+ {
+ foreach ($this->handlers as $handler) {
+ if ($handler->support($user)) {
+ return $handler->generate($user);
+ }
+ }
+ throw new \RuntimeException(sprintf(
+ 'No invoice generator supports member of type %s (id: %d).',
+ $user::class,
+ $user->getId(),
+ ));
+ }
+}
diff --git a/sources/AppBundle/Association/CompanyMembership/SubscriptionManagement.php b/sources/AppBundle/Association/CompanyMembership/SubscriptionManagement.php
index 1c27c35c4..576afb378 100644
--- a/sources/AppBundle/Association/CompanyMembership/SubscriptionManagement.php
+++ b/sources/AppBundle/Association/CompanyMembership/SubscriptionManagement.php
@@ -4,36 +4,37 @@
namespace AppBundle\Association\CompanyMembership;
-use Afup\Site\Association\Cotisations;
+use AppBundle\MembershipFee\MembershipFeeService;
use Afup\Site\Utils\Utils;
use AppBundle\Association\MemberType;
use AppBundle\Association\Model\CompanyMember;
+use AppBundle\MembershipFee\Model\MembershipFee;
final readonly class SubscriptionManagement
{
- public function __construct(private Cotisations $cotisations) {}
+ public function __construct(private MembershipFeeService $membershipFeeService) {}
- public function createInvoiceForInscription(CompanyMember $company, $numberOfMembers): array
+ public function createInvoiceForInscription(CompanyMember $company, int $numberOfMembers): array
{
- $endSubscription = $this->cotisations->finProchaineCotisation(false);
+ $endSubscription = $this->membershipFeeService->getNextSubscriptionExpiration(null);
// Create the invoice
- $this->cotisations->ajouter(
+ $this->membershipFeeService->ajouter(
MemberType::MemberCompany,
$company->getId(),
ceil($numberOfMembers / AFUP_PERSONNE_MORALE_SEUIL) * AFUP_COTISATION_PERSONNE_MORALE * (1 + Utils::MEMBERSHIP_FEE_VAT_RATE),
null,
null,
- (new \DateTime())->format('U'),
- $endSubscription->format('U'),
+ (new \DateTime())->getTimestamp(),
+ $endSubscription->getTimestamp(),
'',
);
- $subscriptionArray = $this->cotisations->obtenirDerniere(MemberType::MemberCompany, $company->getId());
+ $subscription = $this->membershipFeeService->getLatestByUserTypeAndId(MemberType::MemberCompany, $company->getId());
- if ($subscriptionArray === false) {
+ if (!$subscription instanceof MembershipFee) {
throw new \RuntimeException('An error occured');
}
- return ['invoice' => $subscriptionArray['numero_facture'], 'token' => $subscriptionArray['token']];
+ return ['invoice' => $subscription->getInvoiceNumber(), 'token' => $subscription->getToken()];
}
}
diff --git a/sources/AppBundle/Association/UserMembership/SeniorityComputer.php b/sources/AppBundle/Association/UserMembership/SeniorityComputer.php
index 371211a5d..39860457b 100644
--- a/sources/AppBundle/Association/UserMembership/SeniorityComputer.php
+++ b/sources/AppBundle/Association/UserMembership/SeniorityComputer.php
@@ -4,18 +4,20 @@
namespace AppBundle\Association\UserMembership;
-use Afup\Site\Association\Cotisations;
use AppBundle\Association\MemberType;
use AppBundle\Association\Model\CompanyMember;
use AppBundle\Association\Model\User;
+use AppBundle\MembershipFee\Model\MembershipFee;
+use AppBundle\MembershipFee\Model\Repository\MembershipFeeRepository;
+use CCMBenchmark\Ting\Repository\Collection;
class SeniorityComputer
{
- public function __construct(private readonly Cotisations $cotisations) {}
+ public function __construct(private readonly MembershipFeeRepository $membershipFeeRepository) {}
public function computeCompany(CompanyMember $companyMember)
{
- $cotis = $this->cotisations->obtenirListe(MemberType::MemberCompany, $companyMember->getId());
+ $cotis = $this->membershipFeeRepository->getListByUserTypeAndId(MemberType::MemberCompany, $companyMember->getId());
$infos = $this->computeFromCotisationsAndReturnInfos($cotis);
@@ -24,7 +26,7 @@ public function computeCompany(CompanyMember $companyMember)
public function computeCompanyAndReturnInfos(CompanyMember $companyMember): array
{
- $cotis = $this->cotisations->obtenirListe(MemberType::MemberCompany, $companyMember->getId());
+ $cotis = $this->membershipFeeRepository->getListByUserTypeAndId(MemberType::MemberCompany, $companyMember->getId());
return $this->computeFromCotisationsAndReturnInfos($cotis);
}
@@ -38,20 +40,23 @@ public function compute(User $user)
public function computeAndReturnInfos(User $user): array
{
- $cotis = $this->cotisations->obtenirListe(MemberType::MemberPhysical, $user->getId());
+ $cotis = $this->membershipFeeRepository->getListByUserTypeAndId(MemberType::MemberPhysical, $user->getId());
return $this->computeFromCotisationsAndReturnInfos($cotis);
}
- private function computeFromCotisationsAndReturnInfos(array $cotisations): array
+ /**
+ * @param Collection $cotisations
+ */
+ private function computeFromCotisationsAndReturnInfos(Collection $cotisations): array
{
$now = new \DateTime();
$diffs = [];
$years = [];
foreach ($cotisations as $coti) {
- $from = new \DateTimeImmutable('@' . $coti['date_debut']);
- $to = new \DateTimeImmutable('@' . $coti['date_fin']);
+ $from = $coti->getStartDate();
+ $to = $coti->getEndDate();
$to = min($now, $to);
$diffs[] = $from->diff($to);
$years[] = $from->format('Y');
diff --git a/sources/AppBundle/Association/UserMembership/UserService.php b/sources/AppBundle/Association/UserMembership/UserService.php
index caa9375d3..149b2a022 100644
--- a/sources/AppBundle/Association/UserMembership/UserService.php
+++ b/sources/AppBundle/Association/UserMembership/UserService.php
@@ -4,7 +4,7 @@
namespace AppBundle\Association\UserMembership;
-use Afup\Site\Association\Cotisations;
+use AppBundle\MembershipFee\MembershipFeeService;
use AppBundle\Association\MemberType;
use AppBundle\Association\Model\Repository\UserRepository;
use AppBundle\Association\Model\User;
@@ -12,6 +12,7 @@
use AppBundle\Email\Mailer\MailUser;
use AppBundle\Email\Mailer\MailUserFactory;
use AppBundle\Email\Mailer\Message;
+use AppBundle\MembershipFee\Model\MembershipFee;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
@@ -23,7 +24,7 @@ public function __construct(
private readonly UserRepository $userRepository,
private readonly Mailer $mailer,
private readonly UrlGeneratorInterface $urlGenerator,
- private readonly Cotisations $cotisations,
+ private readonly MembershipFeeService $membershipFeeService,
private readonly UserPasswordHasherInterface $passwordHasher,
) {}
@@ -85,10 +86,7 @@ public function sendWelcomeEmail(User $user): bool
return $this->mailer->send($message);
}
- /**
- * @return array
- */
- public function getLastSubscription(User $user)
+ public function getLastSubscription(User $user): ?MembershipFee
{
if ($user->getCompanyId()) {
$id = $user->getCompanyId();
@@ -98,6 +96,6 @@ public function getLastSubscription(User $user)
$personType = MemberType::MemberPhysical;
}
- return $this->cotisations->obtenirDerniere($personType, $id);
+ return $this->membershipFeeService->getLatestByUserTypeAndId($personType, $id);
}
}
diff --git a/sources/AppBundle/Command/PayboxCallbackSimulatorCommand.php b/sources/AppBundle/Command/PayboxCallbackSimulatorCommand.php
index a541402f5..f6fe35e49 100644
--- a/sources/AppBundle/Command/PayboxCallbackSimulatorCommand.php
+++ b/sources/AppBundle/Command/PayboxCallbackSimulatorCommand.php
@@ -4,7 +4,8 @@
namespace AppBundle\Command;
-use Afup\Site\Association\Cotisations;
+use AppBundle\MembershipFee\MembershipFeeService;
+use AppBundle\MembershipFee\OnlinePaymentHandler;
use AppBundle\Association\MemberType;
use AppBundle\Event\Model\Event;
use AppBundle\Event\Model\Invoice;
@@ -30,7 +31,8 @@ class PayboxCallbackSimulatorCommand extends Command
public function __construct(
private readonly InvoiceRepository $invoiceRepository,
private readonly EventRepository $eventRepository,
- private readonly Cotisations $cotisations,
+ private readonly MembershipFeeService $membershipFeeService,
+ private readonly OnlinePaymentHandler $onlinePaymentHandler,
private readonly UrlGeneratorInterface $urlGenerator,
) {
parent::__construct();
@@ -104,8 +106,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int
private function callCotisation(string $cmd, string $status): string
{
- $account = $this->cotisations->getAccountFromCmd($cmd);
- $cotisation = $this->cotisations->obtenirDerniere(MemberType::from($account['type']), $account['id']);
+ $account = $this->onlinePaymentHandler->getAccountFromCmd($cmd);
+ $cotisation = $this->membershipFeeService->getLatestByUserTypeAndId(MemberType::from($account['type']), $account['id']);
if (!$cotisation) {
throw new \RuntimeException(
sprintf('Cotisation non trouvée avec ce CMD: %s', $cmd),
@@ -113,7 +115,7 @@ private function callCotisation(string $cmd, string $status): string
}
$url = $this->urlGenerator->generate('membership_payment');
- return $this->buildUrl($url, (float) $cotisation['montant'], $cmd, $status);
+ return $this->buildUrl($url, $cotisation->getAmount(), $cmd, $status);
}
private function callInvoice(string $cmd, string $status): string
diff --git a/sources/AppBundle/Controller/Admin/Accounting/MembershipFee/DownloadMembershipFeeInvoiceAction.php b/sources/AppBundle/Controller/Admin/Accounting/MembershipFee/DownloadMembershipFeeInvoiceAction.php
index c36d21d3f..60f9206ff 100644
--- a/sources/AppBundle/Controller/Admin/Accounting/MembershipFee/DownloadMembershipFeeInvoiceAction.php
+++ b/sources/AppBundle/Controller/Admin/Accounting/MembershipFee/DownloadMembershipFeeInvoiceAction.php
@@ -4,7 +4,7 @@
namespace AppBundle\Controller\Admin\Accounting\MembershipFee;
-use Afup\Site\Association\Cotisations;
+use AppBundle\MembershipFee\MembershipFeeInvoicePdfGenerator;
use AppBundle\Association\MemberType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
@@ -12,13 +12,13 @@
class DownloadMembershipFeeInvoiceAction extends AbstractController
{
public function __construct(
- private readonly Cotisations $cotisations,
+ private readonly MembershipFeeInvoicePdfGenerator $pdfGenerator,
) {}
public function __invoke(MemberType $memberType, int $memberId, int $membershipFeeId): Response
{
ob_start();
- $this->cotisations->genererFacture($membershipFeeId);
+ $this->pdfGenerator->genererFacture($membershipFeeId);
$pdf = ob_get_clean();
$response = new Response($pdf);
diff --git a/sources/AppBundle/Controller/Admin/Accounting/MembershipFee/SendMembershipFeeInvoiceAction.php b/sources/AppBundle/Controller/Admin/Accounting/MembershipFee/SendMembershipFeeInvoiceAction.php
index aad3df00d..cab1504f0 100644
--- a/sources/AppBundle/Controller/Admin/Accounting/MembershipFee/SendMembershipFeeInvoiceAction.php
+++ b/sources/AppBundle/Controller/Admin/Accounting/MembershipFee/SendMembershipFeeInvoiceAction.php
@@ -4,26 +4,22 @@
namespace AppBundle\Controller\Admin\Accounting\MembershipFee;
-use Afup\Site\Association\Cotisations;
+use AppBundle\MembershipFee\MembershipFeeMailer;
use AppBundle\Association\MemberType;
-use AppBundle\Association\Model\Repository\UserRepository;
use AppBundle\AuditLog\Audit;
-use AppBundle\Email\Mailer\Mailer;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
class SendMembershipFeeInvoiceAction extends AbstractController
{
public function __construct(
- private readonly Mailer $mailer,
- private readonly UserRepository $userRepository,
- private readonly Cotisations $cotisations,
+ private readonly MembershipFeeMailer $membershipFeeMailer,
private readonly Audit $audit,
) {}
public function __invoke(MemberType $memberType, int $memberId, int $membershipFeeId): Response
{
- if ($this->cotisations->envoyerFacture($membershipFeeId, $this->mailer, $this->userRepository)) {
+ if ($this->membershipFeeMailer->envoyerFacture($membershipFeeId)) {
$this->audit->log('Envoi par email de la facture pour la cotisation n°' . $membershipFeeId);
$this->addFlash('notice', 'La facture a été envoyée');
} else {
diff --git a/sources/AppBundle/Controller/Website/Member/IndexAction.php b/sources/AppBundle/Controller/Website/Member/IndexAction.php
index d5ac0ed0f..e9a7087ca 100644
--- a/sources/AppBundle/Controller/Website/Member/IndexAction.php
+++ b/sources/AppBundle/Controller/Website/Member/IndexAction.php
@@ -10,6 +10,7 @@
use AppBundle\Association\UserMembership\BadgesComputer;
use AppBundle\Association\UserMembership\UserService;
use AppBundle\GeneralMeeting\GeneralMeetingRepository;
+use AppBundle\MembershipFee\Model\MembershipFee;
use AppBundle\Security\Authentication;
use AppBundle\Twig\ViewRenderer;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -38,8 +39,8 @@ public function __invoke(): Response
$cotisation = $userService->getLastSubscription($user);
$dateFinCotisation = null;
- if ($cotisation) {
- $dateFinCotisation = new \DateTimeImmutable('@' . $cotisation['date_fin']);
+ if ($cotisation instanceof MembershipFee) {
+ $dateFinCotisation = $cotisation->getEndDate();
}
$daysBeforeMembershipExpiration = $user->getDaysBeforeMembershipExpiration();
diff --git a/sources/AppBundle/Controller/Website/Membership/Fee/DownloadAction.php b/sources/AppBundle/Controller/Website/Membership/Fee/DownloadAction.php
index 61090b571..768a8a481 100644
--- a/sources/AppBundle/Controller/Website/Membership/Fee/DownloadAction.php
+++ b/sources/AppBundle/Controller/Website/Membership/Fee/DownloadAction.php
@@ -4,7 +4,7 @@
namespace AppBundle\Controller\Website\Membership\Fee;
-use Afup\Site\Association\Cotisations;
+use AppBundle\MembershipFee\MembershipFeeInvoicePdfGenerator;
use Afup\Site\Droits;
use AppBundle\Association\MemberType;
use AppBundle\Association\Model\CompanyMember;
@@ -12,6 +12,8 @@
use AppBundle\Association\Model\Repository\UserRepository;
use AppBundle\Association\Model\User;
use AppBundle\AuditLog\Audit;
+use AppBundle\MembershipFee\Model\Repository\MembershipFeeRepository;
+use AppBundle\Security\MembershipFeeVoter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\Request;
@@ -23,7 +25,8 @@ final class DownloadAction extends AbstractController
public function __construct(
private readonly UserRepository $userRepository,
private readonly CompanyMemberRepository $companyMemberRepository,
- private readonly Cotisations $cotisations,
+ private readonly MembershipFeeRepository $membershipFeeRepository,
+ private readonly MembershipFeeInvoicePdfGenerator $pdfGenerator,
private readonly Droits $droits,
private readonly Audit $audit,
) {}
@@ -31,28 +34,28 @@ public function __construct(
public function __invoke(Request $request): BinaryFileResponse
{
$identifiant = $this->droits->obtenirIdentifiant();
- $id = $request->get('id');
+ $id = $request->query->getInt('id');
- if (false === $this->cotisations->isCurrentUserAllowedToReadInvoice($id)) {
+ if (false === $this->isGranted(MembershipFeeVoter::READ_INVOICE, $id)) {
$this->audit->log("L'utilisateur id: " . $identifiant . ' a tenté de voir la facture id:' . $id);
throw $this->createAccessDeniedException('Cette facture ne vous appartient pas, vous ne pouvez la visualiser.');
}
$tempfile = tempnam(sys_get_temp_dir(), 'membership_fee_download');
- $numeroFacture = $this->cotisations->genererFacture($id, $tempfile);
- $cotisation = $this->cotisations->obtenir($id);
+ $numeroFacture = $this->pdfGenerator->genererFacture($id, $tempfile);
+ $membershipFee = $this->membershipFeeRepository->get($id);
- if ($cotisation['type_personne'] == MemberType::MemberCompany->value) {
- $company = $this->companyMemberRepository->get($cotisation['id_personne']);
+ if ($membershipFee->getUserType() === MemberType::MemberCompany) {
+ $company = $this->companyMemberRepository->get($membershipFee->getUserId());
Assert::isInstanceOf($company, CompanyMember::class);
$patternPrefix = $company->getCompanyName();
} else {
- $user = $this->userRepository->get($cotisation['id_personne']);
+ $user = $this->userRepository->get($membershipFee->getUserId());
Assert::isInstanceOf($user, User::class);
$patternPrefix = $user->getLastName();
}
- $pattern = str_replace(' ', '', $patternPrefix) . '_' . $numeroFacture . '_' . date('dmY', (int) $cotisation['date_debut']) . '.pdf';
+ $pattern = str_replace(' ', '', $patternPrefix) . '_' . $numeroFacture . '_' . $membershipFee->getStartDate()->format('dmY') . '.pdf';
$response = new BinaryFileResponse($tempfile, Response::HTTP_OK, [], false);
$response->deleteFileAfterSend(true);
diff --git a/sources/AppBundle/Controller/Website/Membership/Fee/IndexAction.php b/sources/AppBundle/Controller/Website/Membership/Fee/IndexAction.php
index 919b5e514..dcf795717 100644
--- a/sources/AppBundle/Controller/Website/Membership/Fee/IndexAction.php
+++ b/sources/AppBundle/Controller/Website/Membership/Fee/IndexAction.php
@@ -4,15 +4,18 @@
namespace AppBundle\Controller\Website\Membership\Fee;
-use Afup\Site\Association\Cotisations;
+use AppBundle\MembershipFee\MembershipFeeService;
use Afup\Site\Droits;
use Afup\Site\Utils\Utils;
use Afup\Site\Utils\Vat;
+use AppBundle\Security\MembershipFeeVoter;
use AppBundle\Association\MembershipFeeReferenceGenerator;
use AppBundle\Association\MemberType;
use AppBundle\Association\Model\Repository\CompanyMemberRepository;
use AppBundle\Association\Model\Repository\UserRepository;
use AppBundle\Association\UserMembership\UserService;
+use AppBundle\MembershipFee\Model\MembershipFee;
+use AppBundle\MembershipFee\Model\Repository\MembershipFeeRepository;
use AppBundle\Payment\PayboxBilling;
use AppBundle\Payment\PayboxFactory;
use AppBundle\Twig\ViewRenderer;
@@ -28,7 +31,8 @@ public function __construct(
private readonly CompanyMemberRepository $companyMemberRepository,
private readonly UserService $userService,
private readonly PayboxFactory $payboxFactory,
- private readonly Cotisations $cotisations,
+ private readonly MembershipFeeService $membershipFeeService,
+ private readonly MembershipFeeRepository $membershipFeeRepository,
private readonly Droits $droits,
) {}
@@ -43,34 +47,27 @@ public function __invoke(): Response
$now = new \DateTime('now');
$isSubjectedToVat = Vat::isSubjectedToVat($now);
- if (!$cotisation) {
+ if (!$cotisation instanceof MembershipFee) {
$message = '';
} else {
- $endSubscription = $this->cotisations->finProchaineCotisation($cotisation);
+ $endSubscription = $this->membershipFeeService->getNextSubscriptionExpiration($cotisation);
$message = sprintf(
'Votre dernière cotisation -- %s € -- est valable jusqu\'au %s.
Si vous renouvelez votre cotisation maintenant, celle-ci sera valable jusqu\'au %s.',
- number_format((float) $cotisation['montant'], 2, ',', ' '),
- date("d/m/Y", (int) $cotisation['date_fin']),
+ number_format((float) $cotisation->getAmount(), 2, ',', ' '),
+ $cotisation->getEndDate()->format('d/m/Y'),
$endSubscription->format('d/m/Y'),
);
}
- $cotisations_physique = $this->cotisations->obtenirListe(MemberType::MemberPhysical, $user->getId());
- $cotisations_morale = $this->cotisations->obtenirListe(MemberType::MemberCompany, $user->getCompanyId());
+ $cotisations_physique = $this->membershipFeeRepository->getListByUserTypeAndId(MemberType::MemberPhysical, $user->getId());
+ $cotisations_morale = $this->membershipFeeRepository->getListByUserTypeAndId(MemberType::MemberCompany, $user->getCompanyId());
- if (is_array($cotisations_morale) && is_array($cotisations_physique)) {
- $liste_cotisations = array_merge($cotisations_physique, $cotisations_morale);
- } elseif (is_array($cotisations_morale)) {
- $liste_cotisations = $cotisations_morale;
- } elseif (is_array($cotisations_physique)) {
- $liste_cotisations = $cotisations_physique;
- } else {
- $liste_cotisations = [];
- }
+ /** @var array $liste_cotisations */
+ $liste_cotisations = array_merge(iterator_to_array($cotisations_physique), iterator_to_array($cotisations_morale));
foreach ($liste_cotisations as $k => $cotisation) {
- $liste_cotisations[$k]['telecharger_facture'] = $this->cotisations->isCurrentUserAllowedToReadInvoice($cotisation['id']);
+ $cotisation->setDownloadInvoice($this->isGranted(MembershipFeeVoter::READ_INVOICE, (string) $cotisation->getId()));
}
if ($user->getCompanyId() > 0) {
@@ -112,7 +109,7 @@ public function __invoke(): Response
'isSubjectedToVat' => $isSubjectedToVat,
'title' => 'Ma cotisation',
'cotisations' => $liste_cotisations,
- 'time' => time(),
+ 'time' => new \DateTime(),
'montant' => $montant,
'libelle' => $libelle,
'paybox' => $paybox,
diff --git a/sources/AppBundle/Controller/Website/Membership/Fee/SendMailAction.php b/sources/AppBundle/Controller/Website/Membership/Fee/SendMailAction.php
index 78484f1bd..f848be546 100644
--- a/sources/AppBundle/Controller/Website/Membership/Fee/SendMailAction.php
+++ b/sources/AppBundle/Controller/Website/Membership/Fee/SendMailAction.php
@@ -4,11 +4,10 @@
namespace AppBundle\Controller\Website\Membership\Fee;
-use Afup\Site\Association\Cotisations;
+use AppBundle\MembershipFee\MembershipFeeMailer;
use Afup\Site\Droits;
-use AppBundle\Association\Model\Repository\UserRepository;
use AppBundle\AuditLog\Audit;
-use AppBundle\Email\Mailer\Mailer;
+use AppBundle\Security\MembershipFeeVoter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
@@ -16,9 +15,7 @@
final class SendMailAction extends AbstractController
{
public function __construct(
- private readonly UserRepository $userRepository,
- private readonly Mailer $mailer,
- private readonly Cotisations $cotisations,
+ private readonly MembershipFeeMailer $membershipFeeMailer,
private readonly Droits $droits,
private readonly Audit $audit,
) {}
@@ -26,14 +23,14 @@ public function __construct(
public function __invoke(Request $request): RedirectResponse
{
$identifiant = $this->droits->obtenirIdentifiant();
- $id = $request->get('id');
+ $id = $request->query->getInt('id');
- if (false === $this->cotisations->isCurrentUserAllowedToReadInvoice($id)) {
+ if (false === $this->isGranted(MembershipFeeVoter::READ_INVOICE, $id)) {
$this->audit->log("L'utilisateur id: " . $identifiant . ' a tenté de voir la facture id:' . $id);
throw $this->createAccessDeniedException('Cette facture ne vous appartient pas, vous ne pouvez la visualiser.');
}
- if ($this->cotisations->envoyerFacture($id, $this->mailer, $this->userRepository)) {
+ if ($this->membershipFeeMailer->envoyerFacture($id)) {
$this->audit->log('Envoi par email de la facture pour la cotisation n°' . $id);
$this->addFlash('success', 'La facture a été envoyée par mail');
} else {
diff --git a/sources/AppBundle/Controller/Website/Membership/GeneralMeeting/IndexAction.php b/sources/AppBundle/Controller/Website/Membership/GeneralMeeting/IndexAction.php
index 6e442eca4..a6c7c0b60 100644
--- a/sources/AppBundle/Controller/Website/Membership/GeneralMeeting/IndexAction.php
+++ b/sources/AppBundle/Controller/Website/Membership/GeneralMeeting/IndexAction.php
@@ -49,7 +49,7 @@ public function __invoke(Request $request): Response
$generalMeetingPlanned = $generalMeetingRepository->hasGeneralMeetingPlanned();
$cotisation = $userService->getLastSubscription($user);
- $needsMembersheepFeePayment = $latestDate->getTimestamp() > strtotime("+14 day", (int) $cotisation['date_fin']);
+ $needsMembersheepFeePayment = $latestDate->getTimestamp() > strtotime("+14 day", $cotisation->getEndDate()->getTimestamp());
if ($needsMembersheepFeePayment) {
return $this->view->render('admin/association/membership/generalmeeting_membersheepfee.html.twig', [
diff --git a/sources/AppBundle/Controller/Website/Membership/InvoiceAction.php b/sources/AppBundle/Controller/Website/Membership/InvoiceAction.php
index 718290a78..d65d1fe1a 100644
--- a/sources/AppBundle/Controller/Website/Membership/InvoiceAction.php
+++ b/sources/AppBundle/Controller/Website/Membership/InvoiceAction.php
@@ -4,26 +4,29 @@
namespace AppBundle\Controller\Website\Membership;
-use Afup\Site\Association\Cotisations;
+use AppBundle\MembershipFee\MembershipFeeService;
+use AppBundle\MembershipFee\MembershipFeeInvoicePdfGenerator;
+use AppBundle\MembershipFee\Model\MembershipFee;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
final class InvoiceAction extends AbstractController
{
public function __construct(
- private readonly Cotisations $cotisations,
+ private readonly MembershipFeeService $membershipFeeService,
+ private readonly MembershipFeeInvoicePdfGenerator $pdfGenerator,
) {}
public function __invoke(string $invoiceNumber, ?string $token): Response
{
- $invoice = $this->cotisations->getByInvoice($invoiceNumber, $token);
+ $invoice = $this->membershipFeeService->getByInvoice($invoiceNumber, $token);
- if (!$invoice) {
+ if (!$invoice instanceof MembershipFee) {
throw $this->createNotFoundException(sprintf('Could not find the invoice "%s" with token "%s"', $invoiceNumber, $token));
}
ob_start();
- $this->cotisations->genererFacture($invoice['id']);
+ $this->pdfGenerator->genererFacture($invoice->getId());
$pdf = ob_get_clean();
$response = new Response($pdf);
diff --git a/sources/AppBundle/Controller/Website/Membership/PayboxCallbackAction.php b/sources/AppBundle/Controller/Website/Membership/PayboxCallbackAction.php
index aef8d5b27..454f4d2bb 100644
--- a/sources/AppBundle/Controller/Website/Membership/PayboxCallbackAction.php
+++ b/sources/AppBundle/Controller/Website/Membership/PayboxCallbackAction.php
@@ -4,12 +4,14 @@
namespace AppBundle\Controller\Website\Membership;
-use Afup\Site\Association\Cotisations;
+use AppBundle\MembershipFee\MembershipFeeService;
+use AppBundle\MembershipFee\MembershipFeeMailer;
+use AppBundle\MembershipFee\OnlinePaymentHandler;
use AppBundle\Association\Event\NewMemberEvent;
use AppBundle\Association\MemberType;
-use AppBundle\Association\Model\Repository\CompanyMemberRepository;
use AppBundle\Association\Model\Repository\UserRepository;
use AppBundle\AuditLog\Audit;
+use AppBundle\MembershipFee\Model\MembershipFee;
use AppBundle\Payment\PayboxResponseFactory;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
@@ -20,15 +22,15 @@
public function __construct(
private EventDispatcherInterface $eventDispatcher,
private UserRepository $userRepository,
- private CompanyMemberRepository $companyMemberRepository,
- private Cotisations $cotisations,
+ private MembershipFeeService $membershipFeeService,
+ private OnlinePaymentHandler $onlinePaymentHandler,
+ private MembershipFeeMailer $membershipFeeMailer,
private Audit $audit,
) {}
public function __invoke(Request $request): Response
{
$payboxResponse = PayboxResponseFactory::createFromRequest($request);
- $this->cotisations->setCompanyMemberRepository($this->companyMemberRepository);
$status = $payboxResponse->getStatus();
$etat = AFUP_COTISATIONS_PAIEMENT_ERREUR;
@@ -45,16 +47,16 @@ public function __invoke(Request $request): Response
}
if ($etat == AFUP_COTISATIONS_PAIEMENT_REGLE) {
- $account = $this->cotisations->getAccountFromCmd($payboxResponse->getCmd());
- $lastCotisation = $this->cotisations->obtenirDerniere(MemberType::from($account['type']), $account['id']);
+ $account = $this->onlinePaymentHandler->getAccountFromCmd($payboxResponse->getCmd());
+ $lastCotisation = $this->membershipFeeService->getLatestByUserTypeAndId(MemberType::from($account['type']), $account['id']);
- if ($lastCotisation === false && $account['type'] == MemberType::MemberPhysical->value) {
+ if (!$lastCotisation instanceof MembershipFee && $account['type'] == MemberType::MemberPhysical->value) {
$user = $this->userRepository->get($account['id']);
$this->eventDispatcher->dispatch(new NewMemberEvent($user));
}
- $this->cotisations->validerReglementEnLigne($payboxResponse->getCmd(), round($payboxResponse->getTotal() / 100, 2), $payboxResponse->getAuthorizationId(), $payboxResponse->getTransactionId());
- $this->cotisations->notifierReglementEnLigneAuTresorier($payboxResponse->getCmd(), round($payboxResponse->getTotal() / 100, 2), $payboxResponse->getAuthorizationId(), $payboxResponse->getTransactionId(), $this->userRepository);
+ $this->onlinePaymentHandler->validerReglementEnLigne($payboxResponse->getCmd(), round($payboxResponse->getTotal() / 100, 2), $payboxResponse->getAuthorizationId(), $payboxResponse->getTransactionId());
+ $this->membershipFeeMailer->notifierReglementEnLigneAuTresorier($payboxResponse->getCmd(), round($payboxResponse->getTotal() / 100, 2), $payboxResponse->getAuthorizationId(), $payboxResponse->getTransactionId());
$this->audit->log("Ajout de la cotisation " . $payboxResponse->getCmd() . " via Paybox.");
}
return new Response();
diff --git a/sources/AppBundle/Controller/Website/Membership/PaymentAction.php b/sources/AppBundle/Controller/Website/Membership/PaymentAction.php
index 50b09708c..73754a102 100644
--- a/sources/AppBundle/Controller/Website/Membership/PaymentAction.php
+++ b/sources/AppBundle/Controller/Website/Membership/PaymentAction.php
@@ -4,9 +4,10 @@
namespace AppBundle\Controller\Website\Membership;
-use Afup\Site\Association\Cotisations;
+use AppBundle\MembershipFee\MembershipFeeService;
use AppBundle\Association\Model\Repository\CompanyMemberRepository;
use AppBundle\Compta\BankAccount\BankAccountFactory;
+use AppBundle\MembershipFee\Model\MembershipFee;
use AppBundle\Payment\PayboxBilling;
use AppBundle\Payment\PayboxFactory;
use AppBundle\Twig\ViewRenderer;
@@ -19,13 +20,16 @@ public function __construct(
private readonly ViewRenderer $view,
private readonly CompanyMemberRepository $companyMemberRepository,
private readonly PayboxFactory $payboxFactory,
- private readonly Cotisations $cotisations,
+ private readonly MembershipFeeService $membershipFeeService,
) {}
public function __invoke(string $invoiceNumber, ?string $token): Response
{
- $invoice = $this->cotisations->getByInvoice($invoiceNumber, $token);
- $company = $this->companyMemberRepository->get($invoice['id_personne']);
+ $invoice = $this->membershipFeeService->getByInvoice($invoiceNumber, $token);
+ $company = null;
+ if ($invoice instanceof MembershipFee) {
+ $company = $this->companyMemberRepository->get($invoice->getUserId());
+ }
if (!$invoice || $company === null) {
throw $this->createNotFoundException(sprintf('Could not find the invoice "%s" with token "%s"', $invoiceNumber, $token));
@@ -35,7 +39,7 @@ public function __invoke(string $invoiceNumber, ?string $token): Response
$paybox = $this->payboxFactory->createPayboxForSubscription(
'F' . $invoiceNumber,
- (float) $invoice['montant'],
+ (float) $invoice->getAmount(),
$company->getEmail(),
$payboxBilling,
);
@@ -45,7 +49,7 @@ public function __invoke(string $invoiceNumber, ?string $token): Response
return $this->view->render('site/company_membership/payment.html.twig', [
'paybox' => $paybox,
'invoice' => $invoice,
- 'bankAccount' => $bankAccountFactory->createApplyableAt(new \DateTimeImmutable('@' . $invoice['date_debut'])),
+ 'bankAccount' => $bankAccountFactory->createApplyableAt(new \DateTimeImmutable('@' . $invoice->getStartDate()->getTimestamp())),
'afup' => [
'raison_sociale' => AFUP_RAISON_SOCIALE,
'adresse' => AFUP_ADRESSE,
diff --git a/sources/AppBundle/MembershipFee/MembershipFeeInvoicePdfGenerator.php b/sources/AppBundle/MembershipFee/MembershipFeeInvoicePdfGenerator.php
new file mode 100644
index 000000000..18907757a
--- /dev/null
+++ b/sources/AppBundle/MembershipFee/MembershipFeeInvoicePdfGenerator.php
@@ -0,0 +1,191 @@
+membershipFeeRepository->get($idCotisation);
+
+ $userRepository = match ($cotisation->getUserType()) {
+ MemberType::MemberCompany => $this->companyMemberRepository,
+ default => $this->userRepository,
+ };
+
+ /** @var User|CompanyMember $user */
+ $user = $userRepository->get($cotisation->getUserId());
+ $invoiceData = $this->invoiceGenerator->getInvoiceData($user);
+
+ $dateFacture = $cotisation->getInvoiceDate()
+ ?? \DateTimeImmutable::createFromFormat('U', (string) $cotisation->getStartDate()->getTimestamp());
+
+ $isSubjectedToVat = Vat::isSubjectedToVat($dateFacture);
+ $pdf = new PDF_Facture($this->bankAccountFactory->createApplyableAt($dateFacture), $isSubjectedToVat);
+ $pdf->AddPage();
+
+ $pdf->Cell(130, 5);
+ $pdf->Cell(60, 5, 'Le ' . $dateFacture->format('d/m/Y'));
+
+ $pdf->Ln();
+ $pdf->Ln();
+ $pdf->Ln();
+
+ $pdf->SetFont('Arial', 'BU', 10);
+ $pdf->Cell(130, 5, 'Objet : Facture n°' . $cotisation->getInvoiceNumber());
+ $pdf->SetFont('Arial', '', 10);
+
+ $pdf->Ln(10);
+ $pdf->MultiCell(130, 5, $invoiceData->recipient . "\n" . $invoiceData->address . "\n" . $invoiceData->zipcode . "\n" . $invoiceData->city);
+
+ if ($cotisation->getClientReference() !== null) {
+ $pdf->Ln(10);
+ $pdf->MultiCell(180, 5, sprintf(
+ "Référence client : %s",
+ $cotisation->getClientReference(),
+ ));
+ }
+
+ $pdf->Ln(15);
+ $pdf->MultiCell(180, 5, "Facture concernant votre adhésion à l'Association Française des Utilisateurs de PHP (AFUP).");
+
+ $dateFin = $cotisation->getEndDate()->getTimestamp();
+ $montant = $cotisation->getAmount();
+
+ if (false === $isSubjectedToVat) {
+ $pdf->Ln(10);
+ $pdf->SetFillColor(200, 200, 200);
+ $pdf->Cell(50, 5, 'Code', 1, 0, 'L', 1);
+ $pdf->Cell(100, 5, 'Désignation', 1, 0, 'L', 1);
+ $pdf->Cell(40, 5, 'Prix', 1, 0, 'L', 1);
+
+ $pdf->Ln();
+ $pdf->SetFillColor(255, 255, 255);
+ $pdf->Cell(50, 5, 'ADH', 1);
+ $pdf->Cell(100, 5, "Adhésion AFUP jusqu'au " . date('d/m/Y', $dateFin), 1);
+ $pdf->Cell(40, 5, number_format($montant, 2, '.', '') . ' €', 1);
+
+ $pdf->Ln(15);
+ $pdf->Cell(10, 5, 'TVA non applicable - art. 293B du CGI');
+ } else {
+ // On stocke le montant de la cotisation TTC. Pour les personnes morales, on extrait le HT pour éviter d'appliquer deux fois la TVA.
+ if ($cotisation->getUserType() === MemberType::MemberCompany) {
+ $montant = Vat::getRoundedWithoutVatPriceFromPriceWithVat($montant, Utils::MEMBERSHIP_FEE_VAT_RATE);
+ }
+
+ $pdf->Ln(10);
+ $pdf->SetFillColor(200, 200, 200);
+ $pdf->Cell(20, 5, 'Code', 1, 0, 'L', 1);
+ $pdf->Cell(95, 5, 'Désignation', 1, 0, 'L', 1);
+ $pdf->Cell(25, 5, 'Prix HT', 1, 0, 'R', 1);
+ $pdf->Cell(25, 5, 'Taux TVA', 1, 0, 'R', 1);
+ $pdf->Cell(25, 5, 'Prix TTC', 1, 0, 'R', 1);
+
+ if ($cotisation->getUserType() === MemberType::MemberCompany) {
+ [$totalHt, $total] = $this->buildDetailsPersonneMorale($pdf, $montant, $dateFin);
+ } else {
+ [$totalHt, $total] = $this->buildDetailsPersonnePhysique($pdf, $montant, $dateFin);
+ }
+
+ $pdf->Ln();
+ $pdf->SetFillColor(225, 225, 225);
+ $pdf->Cell(165, 5, 'Total HT', 1, 0, 'R', 1);
+ $pdf->Cell(25, 5, $this->formatFactureValue($totalHt) . ' €', 1, 0, 'R', 1);
+
+ $pdf->Ln();
+ $pdf->SetFillColor(255, 255, 255);
+ $pdf->Cell(165, 5, 'Total TVA 20%', 1, 0, 'R', 1);
+ $pdf->Cell(25, 5, $this->formatFactureValue($total - $totalHt) . ' €', 1, 0, 'R', 1);
+
+ $pdf->Ln();
+ $pdf->SetFillColor(225, 225, 225);
+ $pdf->Cell(165, 5, 'Total TTC', 1, 0, 'R', 1);
+ $pdf->Cell(25, 5, $this->formatFactureValue($total) . ' €', 1, 0, 'R', 1);
+ }
+
+ $pdf->Ln(15);
+ $pdf->Cell(10, 5, 'Lors de votre règlement, merci de préciser la mention : "Facture n°' . $cotisation->getInvoiceNumber() . '"');
+
+ if (is_null($chemin)) {
+ $pattern = str_replace(' ', '', $invoiceData->patternPrefix) . '_' . $cotisation->getInvoiceNumber() . '_' . date('dmY', $cotisation->getStartDate()->getTimestamp()) . '.pdf';
+ $pdf->Output($pattern, 'D', true);
+ } else {
+ $pdf->Output($chemin, 'F', true);
+ }
+
+ return $cotisation->getInvoiceNumber();
+ }
+
+ private function buildDetailsPersonneMorale(PDF_Facture $pdf, float $montant, int $dateFin): array
+ {
+ $montantTtc = $montant * (1 + Utils::MEMBERSHIP_FEE_VAT_RATE);
+ $pdf->Ln();
+ $pdf->SetFillColor(255, 255, 255);
+ $pdf->Cell(20, 5, 'ADH', 1);
+ $pdf->Cell(95, 5, "Adhésion AFUP jusqu'au " . date('d/m/Y', $dateFin), 1);
+ $pdf->Cell(25, 5, $this->formatFactureValue($montant) . ' €', 1, 0, 'R');
+ $pdf->Cell(25, 5, (Utils::MEMBERSHIP_FEE_VAT_RATE * 100 . ' %'), 1, 0, 'R');
+ $pdf->Cell(25, 5, $this->formatFactureValue($montantTtc) . ' €', 1, 0, 'R');
+
+ return [$montant, $montantTtc];
+ }
+
+ private function buildDetailsPersonnePhysique(PDF_Facture $pdf, float $montant, int $dateFin): array
+ {
+ $montantFixeHt = 5 / 100 * $montant;
+ $montantFixeTtc = $montantFixeHt * (1 + Utils::MEMBERSHIP_FEE_VAT_RATE);
+ $montantVariable = $montant - $montantFixeTtc;
+
+ $pdf->Ln();
+ $pdf->SetFillColor(255, 255, 255);
+ $pdf->Cell(20, 5, 'ADH-var', 1);
+ $pdf->Cell(95, 5, "Adhésion AFUP jusqu'au " . date('d/m/Y', $dateFin) . ' - part variable', 1);
+ $pdf->Cell(25, 5, $this->formatFactureValue($montantFixeHt) . ' €', 1, 0, 'R');
+ $pdf->Cell(25, 5, (Utils::MEMBERSHIP_FEE_VAT_RATE * 100 . ' %'), 1, 0, 'R');
+ $pdf->Cell(25, 5, $this->formatFactureValue($montantFixeTtc) . ' €', 1, 0, 'R');
+
+ $pdf->Ln();
+ $pdf->SetFillColor(255, 255, 255);
+ $pdf->Cell(20, 5, 'ADH-fixe', 1);
+ $pdf->Cell(95, 5, "Adhésion AFUP jusqu'au " . date('d/m/Y', $dateFin) . ' - part fixe', 1);
+ $pdf->Cell(25, 5, $this->formatFactureValue($montantVariable) . ' €', 1, 0, 'R');
+ $pdf->Cell(25, 5, '0 %', 1, 0, 'R');
+ $pdf->Cell(25, 5, $this->formatFactureValue($montantVariable) . ' €', 1, 0, 'R');
+
+ return [$montantFixeHt + $montantVariable, $montantFixeTtc + $montantVariable];
+ }
+
+ private function formatFactureValue(float $value): string
+ {
+ return number_format($value, 2, ',', ' ');
+ }
+}
diff --git a/sources/AppBundle/MembershipFee/MembershipFeeMailer.php b/sources/AppBundle/MembershipFee/MembershipFeeMailer.php
new file mode 100644
index 000000000..e73974b15
--- /dev/null
+++ b/sources/AppBundle/MembershipFee/MembershipFeeMailer.php
@@ -0,0 +1,134 @@
+membershipFeeRepository->get($idCotisation);
+
+ if ($membership->getUserType() === MemberType::MemberCompany) {
+ $company = $this->companyMemberRepository->get($membership->getUserId());
+ Assert::notNull($company);
+ $contactPhysique = [
+ 'nom' => $company->getLastName(),
+ 'prenom' => $company->getFirstName(),
+ 'email' => $company->getEmail(),
+ ];
+ } else {
+ $user = $this->userRepository->get($membership->getUserId());
+ Assert::notNull($user);
+ $contactPhysique = [
+ 'nom' => $user->getLastName(),
+ 'prenom' => $user->getFirstName(),
+ 'email' => $user->getEmail(),
+ ];
+ }
+ $patternPrefix = $contactPhysique['nom'];
+
+ $corps = "Bonjour,
";
+ $corps .= "Veuillez trouver ci-joint la facture correspondant à votre adhésion à l'AFUP.
";
+ $corps .= "Nous restons à votre disposition pour toute demande complémentaire.
";
+ $corps .= "Le bureau
";
+ $corps .= AFUP_RAISON_SOCIALE . "
";
+ $corps .= AFUP_ADRESSE . "
";
+ $corps .= AFUP_CODE_POSTAL . " " . AFUP_VILLE . "
";
+
+ $cheminFacture = AFUP_CHEMIN_RACINE . 'cache/fact' . $idCotisation . '.pdf';
+ $numeroFacture = $this->pdfGenerator->genererFacture($idCotisation, $cheminFacture);
+ $pattern = str_replace(' ', '', $patternPrefix) . '_' . $numeroFacture . '_' . date('dmY', $membership->getStartDate()->getTimestamp()) . '.pdf';
+
+ $message = new Message('Facture AFUP', null, new MailUser(
+ $contactPhysique['email'],
+ sprintf('%s %s', $contactPhysique['prenom'], $contactPhysique['nom']),
+ ));
+ $message->addAttachment(new Attachment(
+ $cheminFacture,
+ $pattern,
+ 'base64',
+ 'application/pdf',
+ ));
+ $ok = $this->mailer->sendTransactional($message, $corps);
+ @unlink($cheminFacture);
+
+ return $ok;
+ }
+
+ public function notifierReglementEnLigneAuTresorier(string $cmd, float $total, string $autorisation, string $transaction): bool
+ {
+ if (str_starts_with($cmd, 'F')) {
+ // Facture
+ $invoiceNumber = substr($cmd, 1);
+ $cotisation = $this->membershipFeeRepository->getOneBy(['invoiceNumber' => $invoiceNumber]);
+ $typePersonne = $cotisation->getUserType()->value;
+ $idPersonne = $cotisation->getUserId();
+ } else {
+ // Cotisation
+ [$ref, $date, $typePersonne, $idPersonne, $reste] = explode('-', $cmd, 5);
+ }
+
+ $infos = [
+ 'id' => $idPersonne,
+ 'type' => $typePersonne,
+ 'nom' => 'N.C.',
+ 'prenom' => 'N.C.',
+ 'email' => 'N.C.',
+ ];
+
+ if ($typePersonne == MemberType::MemberCompany->value) {
+ if ($company = $this->companyMemberRepository->get($idPersonne)) {
+ $infos['nom'] = $company->getLastName();
+ $infos['prenom'] = $company->getFirstName();
+ $infos['email'] = $company->getEmail();
+ }
+ } else {
+ if ($user = $this->userRepository->get($idPersonne)) {
+ $infos['nom'] = $user->getLastName();
+ $infos['prenom'] = $user->getFirstName();
+ $infos['email'] = $user->getEmail();
+ }
+ }
+
+ $sujet = "Paiement cotisation AFUP";
+
+ $corps = "Bonjour, \n\n";
+ $corps .= "Une cotisation annuelle AFUP a été réglée.\n\n";
+ $corps .= "Personne : " . $infos['nom'] . " " . $infos['prenom'] . " (" . $infos['email'] . ")\n";
+ $corps .= "URL : /admin/accounting/membership-fee/list/" . $infos['type'] . "/" . $infos['id'] . "\n";
+ $corps .= "Commande : " . $cmd . "\n";
+ $corps .= "Total : " . $total . "\n";
+ $corps .= "Autorisation : " . $autorisation . "\n";
+ $corps .= "Transaction : " . $transaction . "\n\n";
+
+ $message = new Message($sujet, new MailUser(MailUser::DEFAULT_SENDER_EMAIL, MailUser::DEFAULT_SENDER_NAME), MailUserFactory::tresorier());
+ return $this->mailer->sendTransactional($message, $corps);
+ }
+}
diff --git a/sources/AppBundle/MembershipFee/MembershipFeeService.php b/sources/AppBundle/MembershipFee/MembershipFeeService.php
new file mode 100644
index 000000000..71bf60f91
--- /dev/null
+++ b/sources/AppBundle/MembershipFee/MembershipFeeService.php
@@ -0,0 +1,142 @@
+setUserType($typePersonne)
+ ->setUserId($idPersonne)
+ ->setAmount($montant)
+ ->setPaymentType($typeReglement !== null ? MembershipFeePayment::from($typeReglement) : null)
+ ->setPaymentDetails($informationsReglement)
+ ->setStartDate(new DateTime('@' . $dateDebut))
+ ->setEndDate(new DateTime('@' . $dateFin))
+ ->setInvoiceNumber($this->membershipFeeRepository->generateInvoiceNumber())
+ ->setToken(base64_encode(random_bytes(30)))
+ ->setComments($commentaires)
+ ->setClientReference($referenceClient)
+ ->setInvoiceDate(new \DateTimeImmutable())
+ ;
+ $this->membershipFeeRepository->save($membershipFee);
+ return true;
+ } catch (\Exception $e) {
+ return false;
+ }
+ }
+
+ public function isAlreadyPaid(string $cmd): bool
+ {
+ return $this->membershipFeeRepository->getOneBy(['paymentDetails' => $cmd]) instanceof MembershipFee;
+ }
+
+ /**
+ * Supprime une cotisation
+ *
+ * @param $id Identifiant de la cotisation à supprimer
+ * @return bool Succès de la suppression
+ */
+ public function supprimer(int $id): bool
+ {
+ try {
+ $cotisation = $this->membershipFeeRepository->get($id);
+ $this->membershipFeeRepository->delete($cotisation);
+ return true;
+ } catch (\Exception $exception) {
+ return false;
+ }
+ }
+
+ /**
+ * Modifie une cotisation
+ *
+ * @param $id Identifiant de la cotisation à modifier
+ * @param $typeReglement Type de règlement (espèces, chèque, virement)
+ * @param $informationsReglement Informations concernant le règlement (numéro de chèque, de virement etc.)
+ * @return bool Succès de la modification
+ */
+ public function updatePayment(int $id, int $typeReglement, string $informationsReglement): bool
+ {
+ return $this->membershipFeeRepository->updatePayment($id, $typeReglement, $informationsReglement) !== false;
+ }
+
+ /**
+ * Retourne la dernière cotisation d'une personne
+ */
+ public function getLatestByUserTypeAndId(MemberType $typePersonne, int $idPersonne): ?MembershipFee
+ {
+ return $this->membershipFeeRepository->getLatestByUserTypeAndId($typePersonne, $idPersonne);
+ }
+
+ public function getNextSubscriptionExpiration(?MembershipFee $cotisation = null): DateTime
+ {
+ $endSubscription = $cotisation ? $cotisation->getEndDate() : new DateTime();
+ $base = $now = new DateTime();
+
+ $year = new DateInterval('P1Y');
+
+ if ($endSubscription > $now) {
+ $base = $endSubscription;
+ }
+
+ $base->add($year);
+ return $base;
+ }
+
+ /**
+ * Renvoit la cotisation demandée
+ *
+ * @param $invoiceId Identifiant de la facture
+ * @param $token Token de la facture. Si null, pas de vérification
+ */
+ public function getByInvoice(string $invoiceId, string $token = null): ?MembershipFee
+ {
+ $criterias = ['invoiceNumber' => $invoiceId];
+ if ($token !== null) {
+ $criterias['token'] = $token;
+ }
+ return $this->membershipFeeRepository->getOneBy($criterias);
+ }
+}
diff --git a/sources/AppBundle/MembershipFee/Model/MembershipFee.php b/sources/AppBundle/MembershipFee/Model/MembershipFee.php
index ada997107..20f132ad7 100644
--- a/sources/AppBundle/MembershipFee/Model/MembershipFee.php
+++ b/sources/AppBundle/MembershipFee/Model/MembershipFee.php
@@ -31,6 +31,8 @@ class MembershipFee implements NotifyPropertyInterface
private ?int $nbReminders = null;
private ?DateTime $lastReminderDate = null;
+ private bool $downloadInvoice = false;
+
public function getId(): ?int
{
return $this->id;
@@ -210,4 +212,14 @@ public function setLastReminderDate(?DateTime $lastReminderDate): self
$this->lastReminderDate = $lastReminderDate;
return $this;
}
+
+ public function canDownloadInvoice(): bool
+ {
+ return $this->downloadInvoice;
+ }
+
+ public function setDownloadInvoice(bool $hasPermission): bool
+ {
+ return $this->downloadInvoice = $hasPermission;
+ }
}
diff --git a/sources/AppBundle/MembershipFee/Model/Repository/MembershipFeeRepository.php b/sources/AppBundle/MembershipFee/Model/Repository/MembershipFeeRepository.php
index bed0966f2..8f7b4d7d3 100644
--- a/sources/AppBundle/MembershipFee/Model/Repository/MembershipFeeRepository.php
+++ b/sources/AppBundle/MembershipFee/Model/Repository/MembershipFeeRepository.php
@@ -8,6 +8,8 @@
use AppBundle\Controller\Admin\Membership\MembershipFeePayment;
use AppBundle\MembershipFee\Model\MembershipFee;
use Aura\SqlQuery\Common\SelectInterface;
+use CCMBenchmark\Ting\Repository\Collection;
+use CCMBenchmark\Ting\Repository\HydratorSingleObject;
use CCMBenchmark\Ting\Repository\Metadata;
use CCMBenchmark\Ting\Repository\MetadataInitializer;
use CCMBenchmark\Ting\Repository\Repository;
@@ -15,6 +17,9 @@
use CCMBenchmark\Ting\Serializer\SerializerFactoryInterface;
use DateTime;
+/**
+ * @extends Repository
+ */
class MembershipFeeRepository extends Repository implements MetadataInitializer
{
public function getMembershipStartingDate(MemberType $memberType, int $idMember): DateTime
@@ -53,6 +58,55 @@ public function generateInvoiceNumber(): string
return 'COTIS-' . date('Y') . '-' . (is_null($result?->number) ? 1 : $result->number);
}
+ public function updatePayment(int $id, int $paymentType, string $paymentInfos): bool
+ {
+ $sql = 'UPDATE';
+ $sql .= ' afup_cotisations ';
+ $sql .= 'SET';
+ $sql .= ' type_reglement= :paymentType,';
+ $sql .= ' informations_reglement=:paymentInfos';
+ $sql .= ' WHERE';
+ $sql .= ' id=' . $id;
+
+ return $this->getQuery($sql)->setParams(['paymentType' => $paymentType, 'paymentInfos' => $paymentInfos, 'id' => $id])->execute();
+ }
+
+ public function getLatestByUserTypeAndId(MemberType $type_personne, int $id_personne): ?MembershipFee
+ {
+ $sql = 'SELECT';
+ $sql .= ' * ';
+ $sql .= 'FROM';
+ $sql .= ' afup_cotisations ';
+ $sql .= 'WHERE';
+ $sql .= ' type_personne=:userType ';
+ $sql .= ' AND id_personne=:userId ';
+ $sql .= 'ORDER BY';
+ $sql .= ' date_fin DESC ';
+ $sql .= 'LIMIT 0, 1 ';
+
+ $collection = $this->getQuery($sql)->setParams(['userType' => $type_personne->value, 'userId' => $id_personne])->query($this->getCollection(new HydratorSingleObject()));
+ if ($collection->count() === 0) {
+ return null;
+ }
+ return $collection->first();
+ }
+
+ /**
+ * @return Collection
+ */
+ public function getListByUserTypeAndId(MemberType $memberType, int $memberId): Collection
+ {
+ $sql = 'SELECT * ';
+ $sql .= 'FROM';
+ $sql .= ' afup_cotisations ';
+ $sql .= 'WHERE';
+ $sql .= ' type_personne=:userType ';
+ $sql .= ' AND id_personne=:userId ';
+ $sql .= 'ORDER BY date_fin DESC';
+
+ return $this->getQuery($sql)->setParams(['userType' => $memberType->value, 'userId' => $memberId])->query($this->getCollection(new HydratorSingleObject()));
+ }
+
public static function initMetadata(SerializerFactoryInterface $serializerFactory, array $options = [])
{
$metadata = new Metadata($serializerFactory);
diff --git a/sources/AppBundle/MembershipFee/OnlinePaymentHandler.php b/sources/AppBundle/MembershipFee/OnlinePaymentHandler.php
new file mode 100644
index 000000000..3ce29b933
--- /dev/null
+++ b/sources/AppBundle/MembershipFee/OnlinePaymentHandler.php
@@ -0,0 +1,75 @@
+ MemberType::MemberCompany->value, 'id' => (int) $arr[2]];
+ }
+
+ // Depuis une cotisation : $cmd=C2023-211120232237-0-5-PAUL-431
+ [$ref, $date, $memberType, $memberId, $stuff] = $arr;
+
+ return ['type' => (int) $memberType, 'id' => (int) $memberId];
+ }
+
+ public function validerReglementEnLigne(string $cmd, float $total, string $autorisation, string $transaction): mixed
+ {
+ $reference = substr($cmd, 0, strlen($cmd) - 4);
+ $verif = substr($cmd, strlen($cmd) - 3, strlen($cmd));
+ $result = false;
+
+ if (str_starts_with($cmd, 'F')) {
+ // This is an invoice ==> we dont have to create a new cotisation, just update the existing one
+ $invoiceNumber = substr($cmd, 1);
+ $cotisation = $this->membershipFeeService->getByInvoice($invoiceNumber);
+
+ $this->membershipFeeService->updatePayment(
+ $cotisation->getId(),
+ AFUP_COTISATIONS_REGLEMENT_ENLIGNE,
+ "autorisation : " . $autorisation . " / transaction : " . $transaction,
+ );
+ } elseif (substr(md5($reference), -3) === strtolower($verif) && !$this->membershipFeeService->isAlreadyPaid($cmd)) {
+ [$ref, $date, $typePersonne, $idPersonne, $reste] = explode('-', $cmd, 5);
+ $dateDebut = mktime(0, 0, 0, (int) substr($date, 2, 2), (int) substr($date, 0, 2), (int) substr($date, 4, 4));
+
+ $cotisation = $this->membershipFeeService->getLatestByUserTypeAndId(MemberType::from((int) $typePersonne), (int) $idPersonne);
+ $dateFinPrecedente = !$cotisation instanceof MembershipFee ? 0 : $cotisation->getEndDate()->getTimestamp();
+
+ if ($dateFinPrecedente > 0) {
+ $dateDebut = strtotime('+1day', $dateFinPrecedente);
+ }
+
+ $dateFin = $this->membershipFeeService->getNextSubscriptionExpiration($cotisation)->getTimestamp();
+ $result = $this->membershipFeeService->ajouter(
+ MemberType::from((int) $typePersonne),
+ (int) $idPersonne,
+ $total,
+ AFUP_COTISATIONS_REGLEMENT_ENLIGNE,
+ $cmd,
+ $dateDebut,
+ $dateFin,
+ "autorisation : " . $autorisation . " / transaction : " . $transaction,
+ );
+ }
+
+ return $result;
+ }
+}
diff --git a/sources/AppBundle/Security/MembershipFeeVoter.php b/sources/AppBundle/Security/MembershipFeeVoter.php
new file mode 100644
index 000000000..cdd7c57a0
--- /dev/null
+++ b/sources/AppBundle/Security/MembershipFeeVoter.php
@@ -0,0 +1,46 @@
+membershipFeeRepository->get((int) $subject);
+ if (!$cotisation instanceof MembershipFee) {
+ return false;
+ }
+
+ if ($cotisation->getUserType() === MemberType::MemberPhysical) {
+ return $cotisation->getUserId() === $this->droits->obtenirIdentifiant();
+ }
+
+ if ($cotisation->getUserType() === MemberType::MemberCompany) {
+ return $this->droits->verifierDroitManagerPersonneMorale($cotisation->getUserId());
+ }
+
+ return false;
+ }
+}
diff --git a/templates/admin/association/membership/membershipfee.html.twig b/templates/admin/association/membership/membershipfee.html.twig
index 3a278063f..2bead8f7a 100644
--- a/templates/admin/association/membership/membershipfee.html.twig
+++ b/templates/admin/association/membership/membershipfee.html.twig
@@ -38,18 +38,18 @@
{% for cotisation in cotisations %}
|
- {{ cotisation.date_debut|date('d/m/Y') }}
+ {{ cotisation.startDate|date('d/m/Y') }}
|
- {{ cotisation.date_fin|date('d/m/Y') }}
- {% if cotisation.date_debut < time and cotisation.date_fin > time %}
+ {{ cotisation.endDate|date('d/m/Y') }}
+ {% if cotisation.startDate < time and cotisation.endDate > time %}
(toujours valide)
{% endif %}
|
- {{ cotisation.montant|number_format(2, ',') }} € |
+ {{ cotisation.amount|number_format(2, ',') }} € |
- {% if cotisation.telecharger_facture %}
-
+ {% if cotisation.canDownloadInvoice %}
+
Télécharger la facture
diff --git a/templates/site/company_membership/payment.html.twig b/templates/site/company_membership/payment.html.twig
index fea0560e5..3bd992b88 100644
--- a/templates/site/company_membership/payment.html.twig
+++ b/templates/site/company_membership/payment.html.twig
@@ -7,17 +7,17 @@
Votre inscription a bien été enregistrée ! Il ne vous reste plus qu'à régler votre cotisation.
- Montant de la cotisation: {{ invoice.montant }} Euros
+ Montant de la cotisation: {{ invoice.amount|number_format(2, '.', ' ') }} Euros
Facture disponible
- Télécharger la facture
+ Télécharger la facture
Régler votre cotisation par Carte Bleue
{{ paybox|raw }}
Régler votre cotisation par chèque
- Libellez le chèque à cet ordre: {{ afup.raison_sociale }}. Indiquez au dos le numéro de facture: {{ invoice.numero_facture }}
+ Libellez le chèque à cet ordre: {{ afup.raison_sociale }}. Indiquez au dos le numéro de facture: {{ invoice.invoiceNumber }}
{{ afup.raison_sociale }}
{{ afup.adresse }}
@@ -25,7 +25,7 @@
Régler votre cotisation par virement bancaire
- Lors d'un règlement par virement bancaire, indiquez ce libellé: {{ invoice.numero_facture }}.
+ Lors d'un règlement par virement bancaire, indiquez ce libellé: {{ invoice.invoiceNumber }}.
diff --git a/tests/unit/Afup/Association/CotisationsTest.php b/tests/unit/AppBundle/MembershipFee/MembershipFeeServiceTest.php
similarity index 69%
rename from tests/unit/Afup/Association/CotisationsTest.php
rename to tests/unit/AppBundle/MembershipFee/MembershipFeeServiceTest.php
index 2a0712941..0721883cc 100644
--- a/tests/unit/Afup/Association/CotisationsTest.php
+++ b/tests/unit/AppBundle/MembershipFee/MembershipFeeServiceTest.php
@@ -2,15 +2,17 @@
declare(strict_types=1);
-namespace Afup\Site\Tests\Association;
+namespace AppBundle\Tests\MembershipFee;
-use Afup\Site\Association\Cotisations;
-use Afup\Site\Utils\Base_De_Donnees;
use AppBundle\Association\MemberType;
+use AppBundle\MembershipFee\MembershipFeeService;
+use AppBundle\MembershipFee\Model\MembershipFee;
+use AppBundle\MembershipFee\Model\Repository\MembershipFeeRepository;
+use AppBundle\MembershipFee\OnlinePaymentHandler;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
-final class CotisationsTest extends TestCase
+final class MembershipFeeServiceTest extends TestCase
{
public static function generateCotisationProvider(): array
{
@@ -37,11 +39,14 @@ public static function generateCotisationProvider(): array
#[DataProvider('generateCotisationProvider')]
public function testFinProchaineCotisation(\DateTimeInterface $dateFin, \DateTimeInterface $expected): void
{
- $bdd = new Base_De_Donnees('', '', '', '');
+ $membershipFeeRepository = $this->createMock(MembershipFeeRepository::class);
- $cotisations = new Cotisations($bdd);
+ $membershipFeeService = new MembershipFeeService($membershipFeeRepository);
- $actual = $cotisations->finProchaineCotisation(['date_fin' => $dateFin->format('U')]);
+ $membershipFee = new MembershipFee();
+ $membershipFee->setEndDate(new \DateTime('@' . $dateFin->format('U')));
+
+ $actual = $membershipFeeService->getNextSubscriptionExpiration($membershipFee);
self::assertEquals($expected->format('Y-m-d'), $actual->format('Y-m-d'));
}
@@ -63,11 +68,13 @@ public static function accountCmdProvider(): array
#[DataProvider('accountCmdProvider')]
public function testGetAccountFromCmd(string $cmd, array $expected): void
{
- $bdd = new Base_De_Donnees('', '', '', '');
+ $membershipFeeRepository = $this->createMock(MembershipFeeRepository::class);
- $cotisations = new Cotisations($bdd);
+ $membershipFeeService = new MembershipFeeService($membershipFeeRepository);
- $actual = $cotisations->getAccountFromCmd($cmd);
+ $actual = (new OnlinePaymentHandler(
+ $membershipFeeService,
+ ))->getAccountFromCmd($cmd);
self::assertEquals($expected, $actual);
}
|