<?php namespace App\Utils; use App\Entity\Adherent; use App\Entity\CotisationTavPrelevementCorrectionSolde; use App\Entity\CotisationTavPrelevementDepassementPlafond; use App\Entity\CotisationTavReversementCorrectionSolde; use App\Entity\Payment; use App\Entity\Siege; use App\Entity\Flux; use App\Entity\CotisationTavReversement; use App\Entity\CotisationTavPrelevement; use App\Enum\MoyenEnum; use App\Utils\CustomEntityManager; use Payum\Core\Request\GetHumanStatus; use Symfony\Component\Security\Core\Security; class TAVCotisationUtils { private $em; private $security; private $operationUtils; public function __construct ( CustomEntityManager $em, Security $security, OperationUtils $operationUtils ) { $this->em = $em; $this->security = $security; $this->operationUtils = $operationUtils; } /** * Check if cotisation already exist this month the the recipient of the given Flux. */ public function checkExistingCotisation(Flux $flux) { $first_day_this_month = date('Y-m-01'); $last_day_this_month = date('Y-m-t'); $existing = $this->em->getRepository(Flux::class)->getTavCotisationsBetweenDates( $flux->getDestinataire(), $first_day_this_month, $last_day_this_month ); return count($existing) > 0; } /** * Returns a descriptive string of existing payment, or empty string. * * @param String $userEmail */ public function checkExistingRecurringPayment($userEmail) { $recurringPayments = $this->em->getRepository(Payment::class)->findBy([ 'isRecurrent' => true, 'clientEmail' => $userEmail, ]); $res = []; foreach($recurringPayments as $p) { if ( $p->getStatus() !== GetHumanStatus::STATUS_FAILED && $p->getStatus() !== GetHumanStatus::STATUS_CANCELED && $p->getStatus() !== GetHumanStatus::STATUS_EXPIRED && $p->getDetails() && array_key_exists('vads_identifier',$p->getDetails()) //some payment without vads_identifier have status NEW, which are not valid payments ) { //Everytime payzen sends a recurring payment notification, notification is //caught by notifyRecurringPaymentAction, which does not update payment status. //This is why we can not rely on $p->getStatus to decide if a recurring //payment is still active or ended or expired. $reason = ""; $monthNumberSinceInitialPaymentDate = -1; if($p->isRecurringPaymentEndedOrExpired($reason,$monthNumberSinceInitialPaymentDate) !== true) { $res[] = ['reason' => $reason, 'monthNumberSinceInitialPaymentDate' => $monthNumberSinceInitialPaymentDate]; } } } return $res; } /** * First method to calculate allowance: * according to a contribution rate defined in user's profile (ProfilDeCotisation). * * Apply the cotisation profile rate to the amount paid * and register the complement as a new flux (only if rate != 1) * * Warning: EntityManager not flushed here. */ public function applyTauxCotisation(Flux $flux) { $profile = $flux->getDestinataire()->getProfilDeCotisation(); $cotisationTaux = $profile->getTauxCotisation(); // don't need to create an other Flux if the rate is 1 if ($cotisationTaux != 1) { // calculate the mlc amount the user will receive $cotisationAmount = $profile->getMontant(); $mlcAmount = round($cotisationAmount * $cotisationTaux); // get the difference between what the user paid and what he•she's supposed to receive $amountDiff = $mlcAmount - $cotisationAmount; if ($flux->getExpediteur() instanceof Siege) { $siege = $flux->getExpediteur(); } else { $siege = $flux->getExpediteur()->getGroupe()->getSiege(); } if ($amountDiff > 0) { // User should receive more than he•she paid: send a new flux to the user to complete its cotisation $fluxCotis = new CotisationTavReversement(); $fluxCotis->setExpediteur($siege); $fluxCotis->setDestinataire($flux->getDestinataire()); $fluxCotis->setMontant($amountDiff); $fluxCotis->setReference("Reversement cotisation après paiement de " . $cotisationAmount . "€ et application du taux " . $cotisationTaux); } else { // User should receive less than he•she paid: fetch the difference from his account $fluxCotis = new CotisationTavPrelevement(); $fluxCotis->setExpediteur($flux->getDestinataire()); $fluxCotis->setDestinataire($siege); $fluxCotis->setMontant(-$amountDiff); $fluxCotis->setReference("Prélèvement cotisation après paiement de " . $cotisationAmount . "€ et application du taux " . $cotisationTaux); } $fluxCotis->setOperateur($flux->getOperateur()); $fluxCotis->setRole($flux->getRole()); $fluxCotis->setMoyen(MoyenEnum::MOYEN_EMLC); $this->em->persist($fluxCotis); $this->operationUtils->executeOperations($fluxCotis); } } /** * Second method to calculate allowance: * allowance based on user's household. * * Rules are as follow: * - 150 emlc for the first person in user's household * - 75 emlc for each other adult * - 75 emlc amount for each dependant child, with a percentage applied if the child is in shared custody: * 25%, 50% or 75% depending on the shared custody arrangement * * Once the full amount is calculated, cap user's balance. * User account balance is capped at twice the amount previously calculated. * * @param Adherent $adherent (by ref) */ public function calculateAllowanceAccordingToHousehold(&$adherent) { // TODO base amounts to param in .env, or in global params ? // base allowance, for one adult $mlcAllowanceAmount = 150; $adultsCount = $adherent->getHouseholdAdultCount(); if ($adultsCount == null) { return; } // increment for each other adult in the household $mlcAllowanceAmount += 75 * ($adultsCount - 1); // increment allowance for each dependant child, depending on the shared custody arrangement $dependentChildren = $adherent->getDependentChildren(); foreach ($dependentChildren as $child) { $childAllowanceAmount = 75; $sharedCustodyPercentage = $child->getSharedCustodyPercentage(); if ($sharedCustodyPercentage != null) { $childAllowanceAmount = $childAllowanceAmount * $sharedCustodyPercentage; } $mlcAllowanceAmount += $childAllowanceAmount; } $adherent->setAllocationAmount($mlcAllowanceAmount); } /** * Method called to create Flux based on allowance amount (for household based allowance). * Only create flux if amount paid != allowance amount. */ public function applyHouseholdAllowance(Flux $flux) { // get allowance $adherent = $flux->getDestinataire(); $cotisationAmount = $flux->getMontant(); // get the mlc amount the user is supposed to receive $mlcAllowanceAmount = $adherent->getAllocationAmount(); // get the difference between what the user paid and what he•she's supposed to receive $amountDiff = $mlcAllowanceAmount - $cotisationAmount; // only create new flux if there is a difference if ($amountDiff != 0) { if ($flux->getExpediteur() instanceof Siege) { $siege = $flux->getExpediteur(); } else { $siege = $flux->getExpediteur()->getGroupe()->getSiege(); } if ($amountDiff > 0) { // User should receive more than he•she paid: send a new flux to the user to complete its cotisation $fluxCotis = new CotisationTavReversement(); $fluxCotis->setExpediteur($siege); $fluxCotis->setDestinataire($adherent); $fluxCotis->setMontant($amountDiff); $fluxCotis->setReference("Versement de l'allocation complémentaire après paiement de " . $cotisationAmount . "€ pour atteindre une allocation de " . $mlcAllowanceAmount . " MonA."); } else { // User should receive less than he•she paid: fetch the difference from his account $fluxCotis = new CotisationTavPrelevement(); $fluxCotis->setExpediteur($adherent); $fluxCotis->setDestinataire($siege); $fluxCotis->setMontant(-$amountDiff); $fluxCotis->setReference("Réduction de l'allocation correspondant à un paiement de " . $cotisationAmount . "€ pour atteindre une allocation de " . $mlcAllowanceAmount . " MonA."); } $fluxCotis->setOperateur($flux->getOperateur()); $fluxCotis->setRole($flux->getRole()); $fluxCotis->setMoyen(MoyenEnum::MOYEN_EMLC); $this->em->persist($fluxCotis); $this->operationUtils->executeOperations($fluxCotis); } } /** * Method called to create Flux based on allowance amount (for household based allowance). */ public function withdrawDownToTheCeiling(Adherent $adherent) { $balance = $adherent->getEmlcAccount()->getBalance(); $ceiling = $adherent->getCeiling(); $siege = $this->em->getRepository(Siege::class)->getTheOne(); // get the amount we want to withdraw $amountDiff = $ceiling - $balance; if ($amountDiff >= 0) { throw new \Exception("Impossible de prélèver : le solde de l'adhérent est inférieur ou égal au plafond."); } $flux = new CotisationTavPrelevementDepassementPlafond(); $flux->setExpediteur($adherent); $flux->setDestinataire($siege); $flux->setMontant(-$amountDiff); $flux->setReference("Prélèvement pour ramener le solde de " . $balance . " MonA sous le plafond de " . $ceiling . " MonA."); $flux->setOperateur($this->security->getUser()); $flux->setRole($this->security->getUser()->getGroups()[0]->__toString()); $flux->setMoyen(MoyenEnum::MOYEN_EMLC); $this->em->persist($flux); $this->operationUtils->executeOperations($flux); return $amountDiff; } /** * Method called to create Flux to fix balance (for household based allowance). */ public function fixBalance(Adherent $adherent, $fixedBalance, $justification) { $balance = $adherent->getEmlcAccount()->getBalance(); $siege = $this->em->getRepository(Siege::class)->getTheOne(); $amountDiff = $fixedBalance - $balance; if ($amountDiff >= 0) { //Accroissement du solde $flux = new CotisationTavReversementCorrectionSolde(); $flux->setExpediteur($siege); $flux->setDestinataire($adherent); $flux->setReference( "Reversement pour corriger le solde de " . $balance . " MonA à " . $fixedBalance . " MonA : " . $justification ); } else { //Réduction du solde $flux = new CotisationTavPrelevementCorrectionSolde(); $flux->setExpediteur($adherent); $flux->setDestinataire($siege); $flux->setReference( "Prélèvement pour corriger le solde de " . $balance . " MonA à " . $fixedBalance . " MonA : " . $justification ); } $flux->setMontant(abs($amountDiff)); $flux->setOperateur($this->security->getUser()); $flux->setRole($this->security->getUser()->getGroups()[0]->__toString()); $flux->setMoyen(MoyenEnum::MOYEN_EMLC); $this->em->persist($flux); $this->operationUtils->executeOperations($flux); } /** * Get the last cotisation of an adhérent * * @param Adherent $adherent * * @return bool|date */ public function getLastTavCotisationForAdherent(?Adherent $adherent) { $cotisations = []; if (null !== $adherent) { $cotisations = $this->em->getRepository(Flux::class)->getLastTavCotisation($adherent); } if (count($cotisations) > 0) { return $cotisations[0]["created_at"]; } return false; } /** * Mark active recurring payment(s) status as canceled. * * @param String $userEmail */ public function cancelExistingRecurringPayment($userEmail) { $recurringPayments = $this->em->getRepository(Payment::class)->findBy([ 'isRecurrent' => true, 'clientEmail' => $userEmail, ]); foreach($recurringPayments as $p) { if ( $p->getStatus() !== GetHumanStatus::STATUS_FAILED && $p->getStatus() !== GetHumanStatus::STATUS_CANCELED && $p->getStatus() !== GetHumanStatus::STATUS_EXPIRED && $p->getDetails() && array_key_exists('vads_identifier',$p->getDetails()) //some payment without vads_identifier have status NEW, which are not valid payments ) { $reason = ""; $monthNumberSinceInitialPaymentDate = -1; if($p->isRecurringPaymentEndedOrExpired($reason,$monthNumberSinceInitialPaymentDate) !== true) { $p->setStatus(GetHumanStatus::STATUS_CANCELED); $this->em->persist($p); } } } $this->em->flush(); } }