TAVCotisationUtils.php 15.1 KB
Newer Older
1 2 3 4
<?php

namespace App\Utils;

5
use App\Entity\Adherent;
6
use App\Entity\CotisationTavPrelevementCorrectionSolde;
Yvon committed
7
use App\Entity\CotisationTavPrelevementDepassementPlafond;
8
use App\Entity\CotisationTavReversementCorrectionSolde;
9
use App\Entity\Payment;
10 11
use App\Entity\Siege;
use App\Entity\Flux;
12 13
use App\Entity\CotisationTavReversement;
use App\Entity\CotisationTavPrelevement;
14
use App\Enum\MoyenEnum;
15
use App\Repository\PaymentRepository;
16
use App\Utils\CustomEntityManager;
17
use Payum\Core\Request\GetHumanStatus;
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
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;
    }

36
    /**
37 38
     * Check if there is an active recurring paiement or if cotisation already exist this month
     * by returning a descriptive reason string if so, else an empty string.
39
     */
40 41
    public function preventCotisationDuplication(Adherent $adherent)
    {
42
        $email = $adherent->getUser()->getEmail();
43
        //Look for existing recurring payment
44
        if($reason = $this->checkExistingRecurringPayment($email)) {
45 46 47 48 49 50
            return implode(" ", array_column($reason,'reason'));
        }
        //Look for existing cotisation
        if ($this->checkExistingCotisation($adherent)) {
            return "Cotisation déjà payée ce mois-ci.";
        }
51 52 53 54 55 56 57 58
        //Look for possible Payzen starting payment (neither finished nor expired yet)
        /* @var PaymentRepository $repo */
        $repo = $this->em->getRepository(Payment::class);
        $foundStartingPaymentTimeout = $repo->findValidStartingPayment($email);
        if ($foundStartingPaymentTimeout) {
            return "Détection d'un possible paiement en cours. Merci de réessayer à partir de "
                . $foundStartingPaymentTimeout->format("H:i:s") . ".";
        }
59 60 61 62 63 64 65
        return "";
    }

    /**
     * Check if cotisation already exist this month.
     */
    private function checkExistingCotisation(Adherent $adherent)
66
    {
Damien Moulard committed
67 68
        $first_day_this_month = date('Y-m-01');
        $last_day_this_month  = date('Y-m-t');
69

Damien Moulard committed
70
        $existing = $this->em->getRepository(Flux::class)->getTavCotisationsBetweenDates(
71
            $adherent,
Damien Moulard committed
72 73 74
            $first_day_this_month,
            $last_day_this_month
        );
75

Damien Moulard committed
76
        return count($existing) > 0;
77
    }
78

79
    /**
80 81
     * Returns an array containing a descriptive string of existing payment
     * as well as other information about the payment, or an empty array.
82 83 84 85
     * 
     * @param String $userEmail
     */
    public function checkExistingRecurringPayment($userEmail)
86 87 88
    {
        $recurringPayments = $this->em->getRepository(Payment::class)->findBy([
            'isRecurrent' => true,
89
            'clientEmail' => $userEmail,
90 91
        ]);

92
        $res = [];
93
        foreach($recurringPayments as $p) {
94 95 96 97
            if (
                $p->getStatus() !== GetHumanStatus::STATUS_FAILED
                && $p->getStatus() !== GetHumanStatus::STATUS_CANCELED
                && $p->getStatus() !== GetHumanStatus::STATUS_EXPIRED
98
                && $p->getDetails()
99
                && array_key_exists('vads_identifier',$p->getDetails()) //some payment without vads_identifier have status NEW, which are not valid payments
100 101 102 103 104
            ) {
                //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.
105
                $reason = "";
106 107 108
                $monthNumberSinceInitialPaymentDate = -1;
                if($p->isRecurringPaymentEndedOrExpired($reason,$monthNumberSinceInitialPaymentDate) !== true) {
                    $res[] = ['reason' => $reason, 'monthNumberSinceInitialPaymentDate' => $monthNumberSinceInitialPaymentDate];
109
                }
110 111 112 113
            }
        }
        return $res;
    }
114

115
    /**
116 117 118
     * First method to calculate allowance: 
     * according to a contribution rate defined in user's profile (ProfilDeCotisation).
     * 
119
     * Apply the cotisation profile rate to the amount paid 
120
     * and register the complement as a new flux (only if rate != 1)
121 122 123 124 125 126 127
     * 
     * Warning: EntityManager not flushed here.
     */
    public function applyTauxCotisation(Flux $flux)
    {
        $profile = $flux->getDestinataire()->getProfilDeCotisation();
        $cotisationTaux = $profile->getTauxCotisation();
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
        
        // 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
146
                $fluxCotis = new CotisationTavReversement();
147 148 149 150 151 152
                $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 
153
                $fluxCotis = new CotisationTavPrelevement();
154 155 156 157 158 159 160 161 162 163 164
                $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);
165 166
        }
    }
167

168 169 170 171 172 173
    /**
     * 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
174 175
     * - 75 emlc for each other adult
     * - 75 emlc amount for each dependant child, with a percentage applied if the child is in shared custody:
176 177 178 179
     * 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.
180 181
     * 
     * @param Adherent $adherent (by ref)
182
     */
183 184
    public function calculateAllowanceAccordingToHousehold(&$adherent) {
        // TODO base amounts to param in .env, or in global params ?
185 186

        // base allowance, for one adult
187
        $mlcAllowanceAmount = 150;
188 189 190

        $adultsCount = $adherent->getHouseholdAdultCount();
        if ($adultsCount == null) {
191
            return;
192 193 194 195 196 197 198 199 200 201
        }

        // 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;

202 203 204
             $sharedCustodyPercentage = $child->getSharedCustodyPercentage();
             if ($sharedCustodyPercentage != null) {
                $childAllowanceAmount = $childAllowanceAmount * $sharedCustodyPercentage;
205 206 207 208 209
             }

            $mlcAllowanceAmount += $childAllowanceAmount;
        }

210 211 212 213
        $adherent->setAllocationAmount($mlcAllowanceAmount);
    }

    /**
Damien Moulard committed
214
     * Method called to create Flux based on allowance amount (for household based allowance).
215
     * Only create flux if amount paid != allowance amount. 
216 217 218
     */
    public function applyHouseholdAllowance(Flux $flux) {
        // get allowance
219 220 221 222 223
        $adherent = $flux->getDestinataire();
        $cotisationAmount = $flux->getMontant();
        
        // get the mlc amount the user is supposed to receive
        $mlcAllowanceAmount = $adherent->getAllocationAmount();
224

225 226 227
        // get the difference between what the user paid and what he•she's supposed to receive
        $amountDiff = $mlcAllowanceAmount - $cotisationAmount;

228 229 230 231 232 233 234
        // 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();
            }
235

236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
            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);
        }
258
    }
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275

    /**
     * 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.");
        }

Yvon committed
276
        $flux = new CotisationTavPrelevementDepassementPlafond();
277 278 279
        $flux->setExpediteur($adherent);
        $flux->setDestinataire($siege);
        $flux->setMontant(-$amountDiff);
Yvon committed
280
        $flux->setReference("Prélèvement pour ramener le solde de " . $balance . " MonA sous le plafond de " . $ceiling . " MonA.");
281 282 283 284 285 286 287 288 289
        $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;
    }

290 291 292 293 294 295 296 297 298 299 300 301
    /**
     * 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
302
            $flux = new CotisationTavReversementCorrectionSolde();
303 304 305 306 307 308 309
            $flux->setExpediteur($siege);
            $flux->setDestinataire($adherent);
            $flux->setReference(
                "Reversement pour corriger le solde de " . $balance . " MonA à " . $fixedBalance . " MonA : " . $justification
            );
        } else {
            //Réduction du solde
310
            $flux = new CotisationTavPrelevementCorrectionSolde();
311 312 313 314 315 316 317 318 319 320 321 322 323 324
            $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);
    }

325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
    /**
     * 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;
    }
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365

    /**
     * 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 = "";
366 367
                $monthNumberSinceInitialPaymentDate = -1;
                if($p->isRecurringPaymentEndedOrExpired($reason,$monthNumberSinceInitialPaymentDate) !== true) {
368 369 370 371 372 373 374
                    $p->setStatus(GetHumanStatus::STATUS_CANCELED);
                    $this->em->persist($p);
                }
            }
        }
        $this->em->flush();
    }
375
}