src/Utils/InvoiceUtils.php line 95

Open in your IDE?
  1. <?php
  2. namespace App\Utils;
  3. use App\Enum\CategoryTypeEnum;
  4. use App\Enum\CreditNoteTypeEnum;
  5. use App\Enum\DiscountCodeTypeEnum;
  6. use App\Enum\DiscountTypeEnum;
  7. use App\Enum\InvoiceStatusEnum;
  8. use App\Enum\OrganizationStatusPayedEnum;
  9. use App\Enum\PaymentRecurrenceEnum;
  10. use App\Enum\PaymentStatusEnum;
  11. use App\Enum\PaymentTypeEnum;
  12. use App\Enum\ProductKeyEnum;
  13. use App\Enum\RefundStatusEnum;
  14. use App\Enum\VATRateEnum;
  15. use App\Service\GocardlessAPI;
  16. use App\Service\InvoiceFilesService;
  17. use App\Traits\SentryNotifyTrait;
  18. use Doctrine\ORM\EntityManagerInterface;
  19. use Evo\Infrastructure\MappingORM\DiscountCode;
  20. use Evo\Infrastructure\MappingORM\Invoice;
  21. use Evo\Infrastructure\MappingORM\Item;
  22. use Evo\Infrastructure\MappingORM\Organization;
  23. use Evo\Infrastructure\MappingORM\Payment;
  24. use Evo\Infrastructure\MappingORM\Product;
  25. use Evo\Infrastructure\Messenger\Message\UpdateInvoiceReferenceStripeMessage;
  26. use Evo\Infrastructure\Repository\InvoiceRepository;
  27. use Knp\Snappy\Pdf;
  28. use Symfony\Component\HttpKernel\KernelInterface;
  29. use Symfony\Component\Messenger\Bridge\AmazonSqs\Transport\AmazonSqsFifoStamp;
  30. use Symfony\Component\Messenger\Envelope;
  31. use Symfony\Component\Messenger\MessageBusInterface;
  32. use SymfonyBundles\RedisBundle\Redis\ClientInterface;
  33. use Twig\Environment;
  34. class InvoiceUtils
  35. {
  36.     use SentryNotifyTrait;
  37.     private EntityManagerInterface $em;
  38.     private GocardlessAPI $gocardlessAPI;
  39.     private MessageBusInterface $bus;
  40.     private KernelInterface $kernel;
  41.     private InvoiceRepository $invoiceRepository;
  42.     private Environment $twig;
  43.     private Pdf $pdf;
  44.     private InvoiceFilesService $invoiceFilesService;
  45.     private ClientInterface $client;
  46.     public function __construct(
  47.         EntityManagerInterface $em,
  48.         GocardlessAPI $gocardlessAPI,
  49.         KernelInterface $kernel,
  50.         InvoiceRepository $invoiceRepository,
  51.         Environment $twig,
  52.         Pdf $pdf,
  53.         InvoiceFilesService $invoiceFilesService,
  54.         MessageBusInterface $bus,
  55.         ClientInterface $client
  56.     ) {
  57.         $this->em $em;
  58.         $this->gocardlessAPI $gocardlessAPI;
  59.         $this->bus $bus;
  60.         $this->kernel $kernel;
  61.         $this->invoiceRepository $invoiceRepository;
  62.         $this->twig $twig;
  63.         $this->pdf $pdf;
  64.         $this->invoiceFilesService $invoiceFilesService;
  65.         $this->client $client;
  66.     }
  67.     public function isHavingWaitingPayment(Invoice $invoice): bool
  68.     {
  69.         foreach ($invoice->getPayments() as $payment) {
  70.             if (PaymentStatusEnum::WAITING === $payment->getStatus()) {
  71.                 return true;
  72.             }
  73.         }
  74.         return false;
  75.     }
  76.     public function setTotalWaitingPayment(Invoice $invoice): float
  77.     {
  78.         $totalWaitingPayment 0;
  79.         foreach ($invoice->getPayments() as $payment) {
  80.             if (PaymentStatusEnum::WAITING === $payment->getStatus()) {
  81.                 $totalWaitingPayment += $payment->getAmount();
  82.             }
  83.         }
  84.         return $totalWaitingPayment;
  85.     }
  86.     public function generateInvoice(Invoice $invoice): ?string
  87.     {
  88.         if (null === $invoice->getOrganization()) {
  89.             return null;
  90.         }
  91.         $publicDir $this->kernel->getProjectDir().'/public/';
  92.         $vat VATRateEnum::VAT_20 === $invoice->getVATRate() ? $invoice->getPriceWithoutTax() * 0.2 0;
  93.         $this->generateNumberForInvoice($invoice);
  94.         $isDisplayDiscount $this->checkItemDiscount($invoice);
  95.         $companyName '';
  96.         /** @var Organization $organization */
  97.         $organization $invoice->getOrganization();
  98.         if ($organization && !$organization->getLegalName()) {
  99.             $user $organization->getUsers()[0];
  100.             $companyName $user $user->getFullName() : '';
  101.         }
  102.         $discountCode 0;
  103.         $discountName '';
  104.         $discountLabel '';
  105.         $isDisplayDiscountInvoice false;
  106.         $isDisplayDiscountItem false;
  107.         $discountCodeType '';
  108.         if (null !== $invoice->getDiscountCode()) {
  109.             $discountCode $invoice->getDiscountCode()->getFirstDiscountConfiguration()->getAmount();
  110.             $discountName $invoice->getDiscountCode()->getPromoCode();
  111.             $discountLabel $invoice->getDiscountCode()->getName();
  112.             $discountCodeType $invoice->getDiscountCode()->getType();
  113.             $isDisplayDiscountInvoice DiscountCodeTypeEnum::INVOICE_DISCOUNT === $invoice->getDiscountCode()->getType();
  114.             $isDisplayDiscountItem in_array($invoice->getDiscountCode()->getType(), [DiscountCodeTypeEnum::PRODUCT_DISCOUNTDiscountCodeTypeEnum::SUBSCRIPTION_DISCOUNT], true);
  115.         }
  116.         $aDomPack = [
  117.             ProductKeyEnum::PACK_DOMISIMPLE,
  118.             ProductKeyEnum::PACK_DIGIPACK,
  119.             ProductKeyEnum::PACK_SCANPACK,
  120.             ProductKeyEnum::PACK_SCANPACK_WITHOUT_REEXPEDITION,
  121.             ProductKeyEnum::PACK_COMPTAPACK,
  122.             ProductKeyEnum::PACK_DOMISCAN,
  123.         ];
  124.         $twigData = [
  125.             'invoice' => $invoice,
  126.             'items' => $invoice->getItems(),
  127.             'publicDir' => $publicDir,
  128.             'companyName' => $companyName,
  129.             'subtotal_without_tax' => $invoice->getPriceWithoutTax(),
  130.             'discount_without_tax' => $invoice->getDiscountWithoutTax(),
  131.             'totalRefundedPayment' => $invoice->getTotalRefundedPayment(),
  132.             'subtotal' => $invoice->getPrice(),
  133.             'subtotal_vat' => $vat,
  134.             'isDisplayDiscount' => $isDisplayDiscount,
  135.             'discountCode' => $discountCode,
  136.             'discountName' => $discountName,
  137.             'discountLabel' => $discountLabel,
  138.             'isDisplayDiscountInvoice' => $isDisplayDiscountInvoice,
  139.             'aDomPack' => $aDomPack,
  140.             'discountCodeType' => $discountCodeType,
  141.             'isDisplayDiscountItem' => $isDisplayDiscountItem,
  142.         ];
  143.         $output $this->pdf->getOutputFromHtml(
  144.             $this->twig->render(
  145.                 'billing/invoice.html.twig',
  146.                 $twigData
  147.             ),
  148.             [
  149.                 'margin-bottom' => '30',
  150.                 'margin-top' => '10',
  151.                 'margin-right' => '10',
  152.                 'margin-left' => '10',
  153.                 'footer-center' => '',
  154.                 'footer-right' => 'Page [page]/[toPage]',
  155.                 'footer-font-name' => 'sans-serif',
  156.                 'footer-font-size' => '7',
  157.                 'footer-html' => "
  158.                     <div style='text-align: center; font-size: 9px; font-family: sans-serif; color: #353535;'>
  159.                     KOAH - DIGIDOM - GROUPE PARAJURIS - 10 rue de Penthi&egrave;vre - 75008 Paris 08 - France
  160.                     <br/>
  161.                     Siret : 797 978 996 00016 - Naf : 7022Z - TVA : FR 86 797978996 - RCS 797 978 996 PARIS
  162.                     <br>
  163.                     SAS au capital de 262 600,00 &euro;
  164.                     <br>
  165.                     Tel : 01 85 53 38 24 - Email : support@digidom.pro - Site internet : www.digidom.pro
  166.                     </div>",
  167.             ]
  168.         );
  169.         return $this->invoiceFilesService->sendToStorage($invoice$output);
  170.     }
  171.     public function checkItemDiscount(Invoice $invoice)
  172.     {
  173.         /** @var Item $item */
  174.         foreach ($invoice->getItems() as $item) {
  175.             if ($item->getDiscountWithoutTax() > 0) {
  176.                 return true;
  177.             }
  178.         }
  179.         return false;
  180.     }
  181.     public function discountCodeCalculation(float $amountDiscountCode $discountCode): float
  182.     {
  183.         $discountAmount 0;
  184.         switch ($discountCode->getDiscountType()) {
  185.             case DiscountTypeEnum::REDUCTION_IN_EURO:
  186.                 $discountAmount = (float) $discountCode->getFirstDiscountConfiguration()->getAmount();
  187.                 break;
  188.             case DiscountTypeEnum::FIXED_PRICE:
  189.                 $discountAmount $amount $discountCode->getFirstDiscountConfiguration()->getAmount();
  190.                 break;
  191.         }
  192.         return $discountAmount;
  193.     }
  194.     public function generatePaymentGocardless(float $amountInvoice $invoicebool $ignoreDomiciliation false): ?\GoCardlessPro\Resources\Payment
  195.     {
  196.         if (!$ignoreDomiciliation && !$invoice->getIsDomiciliation()) {
  197.             $payment $this->getPayment($invoice);
  198.             if (!$payment instanceof Payment) {
  199.                 return null;
  200.             }
  201.         }
  202.         if (!$invoice->getOrganization() || .0 === $amount) {
  203.             return null;
  204.         }
  205.         $organization $invoice->getOrganization();
  206.         $mandateID $organization->getMandatID();
  207.         if (false === $organization->getIsAuthorizedDebit()) {
  208.             return null;
  209.         }
  210.         if ($mandateID && $invoice->getPrice() && !$this->getPayment($invoice)) {
  211.             $paymentGC $this->gocardlessAPI->createPayment(
  212.                 $mandateID,
  213.                 $amount,
  214.                 'Payment '.$invoice->getName()
  215.             );
  216.             if (null !== $paymentGC) {
  217.                 $payment $this->getPayment($invoice);
  218.                 /*
  219.                  * Create payment
  220.                  */
  221.                 if (null === $payment) {
  222.                     $payment = new Payment();
  223.                     $payment->setType(PaymentTypeEnum::GOCARDLESS);
  224.                     $payment->setPaidOutAt(new \DateTime($invoice->getCreatedAt()->format('Y-m-d H:s:i')));
  225.                     $payment->setAmount($invoice->getPrice());
  226.                     $payment->setInvoice($invoice);
  227.                     $payment->setStatus(PaymentStatusEnum::WAITING);
  228.                 }
  229.                 $payment->setReference($paymentGC->id);
  230.                 $this->em->persist($payment);
  231.                 $this->em->flush();
  232.                 return $paymentGC;
  233.             }
  234.             $this->sendSentryMessage('No payment Gocardless has created for mandate '.$mandateID);
  235.         }
  236.         return null;
  237.     }
  238.     public function generatePaymentGocardlessFromPayment(Payment $entity)
  239.     {
  240.         $invoice $entity->getInvoice();
  241.         if (null === $invoice->getOrganization()) {
  242.             return null;
  243.         }
  244.         /** @var Organization $organization */
  245.         $organization $invoice->getOrganization();
  246.         $mandateID $organization->getMandatID();
  247.         if (false === $organization->getIsAuthorizedDebit()) {
  248.             return null;
  249.         }
  250.         $amount = (float) $entity->getAmount();
  251.         if ($mandateID && $amount 0) {
  252.             $paymentGC $this->gocardlessAPI->createPayment(
  253.                 $mandateID,
  254.                 $entity->getAmount(),
  255.                 'Payment '.$invoice->getName()
  256.             );
  257.             if (null !== $paymentGC) {
  258.                 $entity->setReference($paymentGC->id);
  259.                 $this->em->persist($entity);
  260.                 $this->em->flush();
  261.                 return $paymentGC;
  262.             }
  263.         }
  264.         $this->sendSentryMessage('No payment Gocardless has created for mandate '.$mandateID);
  265.         return null;
  266.     }
  267.     /**
  268.      * @return Payment|null
  269.      */
  270.     public function getPayment(Invoice $invoice$type PaymentTypeEnum::GOCARDLESS)
  271.     {
  272.         if (empty($invoice->getPayments()->toArray())) {
  273.             return null;
  274.         }
  275.         /** @var Payment $payment */
  276.         foreach ($invoice->getPayments()->toArray() as $payment) {
  277.             if ($payment->getType() === $type) {
  278.                 return $payment;
  279.             }
  280.         }
  281.         return null;
  282.     }
  283.     /**
  284.      * @return Payment|null
  285.      */
  286.     public function getPaymentSuccess(Invoice $invoice)
  287.     {
  288.         /** @var Payment $payment */
  289.         foreach ($invoice->getPayments()->toArray() as $payment) {
  290.             if (PaymentStatusEnum::SUCCESS === $payment->getStatus()) {
  291.                 return $payment;
  292.             }
  293.         }
  294.         return null;
  295.     }
  296.     /**
  297.      * @param string $type
  298.      */
  299.     public function fixDuplicatePayment(Invoice $invoice$type PaymentTypeEnum::GOCARDLESS)
  300.     {
  301.         $currentePayment null;
  302.         /** @var Payment $payment */
  303.         foreach ($invoice->getPayments()->toArray() as $payment) {
  304.             if ($payment->getType() === $type) {
  305.                 $currentePayment $payment;
  306.                 break;
  307.             }
  308.         }
  309.         foreach ($invoice->getPayments()->toArray() as $payment) {
  310.             if ($payment->getType() === $type && $payment->getId() !== $currentePayment->getId()) {
  311.                 $this->em->remove($payment);
  312.                 $this->em->flush();
  313.             }
  314.         }
  315.     }
  316.     public function countPaymentType(Invoice $invoice$type PaymentTypeEnum::GOCARDLESS)
  317.     {
  318.         $count 0;
  319.         /** @var Payment $payment */
  320.         foreach ($invoice->getPayments()->toArray() as $payment) {
  321.             if ($payment->getType() === $type) {
  322.                 ++$count;
  323.             }
  324.         }
  325.         return $count;
  326.     }
  327.     public function checkInvoice(Invoice $invoicebool $flush false): Invoice
  328.     {
  329.         $totalCreditNote $this->getTotalCreditNote($invoice);
  330.         $totalRefund $this->getTotalRefund($invoice);
  331.         $invoice->setTotalRefund($totalCreditNote);
  332.         $invoice->setTotalRefundedPayment($totalRefund);
  333.         if ($totalCreditNote && $totalCreditNote === $invoice->getPrice()) {
  334.             $invoice->setIsCanceled(true);
  335.         }
  336.         $total_paid $this->getTotalPaid($invoice);
  337.         $invoice->setTotalPaid($total_paid $totalRefund);
  338.         $invoice->setRemainingToPay(round(
  339.             ($invoice->getPrice() - $totalCreditNote) - ($total_paid $totalRefund),
  340.             2
  341.         ));
  342.         $limitReached false;
  343.         $now date('Y-m-d');
  344.         $limitDate $this->getLimitedDate($invoice);
  345.         if ($limitDate $now) {
  346.             $limitReached true;
  347.         }
  348.         if (!$invoice->isModel()) {
  349.             if (0.0 === $invoice->getRemainingToPay()) {
  350.                 $invoice->setStatus(InvoiceStatusEnum::PAID);
  351.             } elseif (
  352.                 $invoice->getRemainingToPay() > &&
  353.                 $limitReached && !$invoice->getIsImported()
  354.             ) {
  355.                 $invoice->setStatus(InvoiceStatusEnum::LATE);
  356.             } elseif ($invoice->getTotalPaid() > && $invoice->getRemainingToPay() > 0) {
  357.                 $invoice->setStatus(InvoiceStatusEnum::PARTIALLY_PAID);
  358.             } elseif ($invoice->getRemainingToPay() < 0) {
  359.                 $invoice->setStatus(InvoiceStatusEnum::OVER_PAID);
  360.             } elseif (0.0 === $invoice->getTotalPaid()) {
  361.                 $invoice->setStatus(InvoiceStatusEnum::SENT);
  362.             }
  363.         }
  364.         if ($flush) {
  365.             $this->em->persist($invoice);
  366.             $this->em->flush();
  367.         }
  368.         return $invoice;
  369.     }
  370.     public function getTotalCreditNote(?Invoice $invoice)
  371.     {
  372.         if (!$invoice instanceof Invoice) {
  373.             return 0;
  374.         }
  375.         $total_refund 0;
  376.         if ($invoice->getCreditNotes()->count() > 0) {
  377.             foreach ($invoice->getCreditNotes() as $creditNote) {
  378.                 if (CreditNoteTypeEnum::REFUND == $creditNote->getType()) {
  379.                     $total_refund += $creditNote->getAmount();
  380.                 }
  381.             }
  382.         }
  383.         return round($total_refund2);
  384.     }
  385.     public function getTotalRefund(?Invoice $invoice)
  386.     {
  387.         if (!$invoice instanceof Invoice) {
  388.             return 0;
  389.         }
  390.         $totalRefund 0;
  391.         if ($invoice->getRefunds()->count() > 0) {
  392.             foreach ($invoice->getRefunds() as $refund) {
  393.                 if (RefundStatusEnum::SUCCESS === $refund->getStatus()) {
  394.                     $totalRefund += $refund->getAmount();
  395.                 }
  396.             }
  397.         }
  398.         return round($totalRefund2);
  399.     }
  400.     public function getTotalPaid(?Invoice $invoice)
  401.     {
  402.         if (!$invoice instanceof Invoice) {
  403.             return 0;
  404.         }
  405.         $total_paid 0;
  406.         $waiting 0;
  407.         if ($invoice->getPayments()->count() > 0) {
  408.             foreach ($invoice->getPayments() as $payment) {
  409.                 if (PaymentStatusEnum::SUCCESS === $payment->getStatus()) {
  410.                     $total_paid += $payment->getAmount();
  411.                 } else {
  412.                     $waiting += $payment->getAmount();
  413.                 }
  414.             }
  415.         }
  416.         // si besoin on pourra distinguer waiting et total paid plus tard
  417.         return round($total_paid2);
  418.     }
  419.     public function getLimitedDate(Invoice $invoice)
  420.     {
  421.         $paymentDeadline $invoice->getPaymentDeadline();
  422.         if (null !== $paymentDeadline) {
  423.             $limitDate $paymentDeadline->format('Y-m-d');
  424.         } else {
  425.             $limitDate date('Y-m-d'strtotime('+30 days'$invoice->getCreatedAt()->getTimestamp()));
  426.         }
  427.         return $limitDate;
  428.     }
  429.     /**
  430.      * @return bool
  431.      */
  432.     public function isFormalityInvoice(Invoice $invoice)
  433.     {
  434.         /** @var Item $item */
  435.         foreach ($invoice->getItems() as $item) {
  436.             /** @var Product $product */
  437.             $product $item->getProduct();
  438.             if ($product) {
  439.                 $category $product->getCategory();
  440.                 if (null !== $category && CategoryTypeEnum::FORMALITES === $category->getType()) {
  441.                     return true;
  442.                 }
  443.             }
  444.         }
  445.         return false;
  446.     }
  447.     public function paid(Invoice $invoice$type PaymentTypeEnum::STRIPE$strypeRefence null)
  448.     {
  449.         if (PaymentTypeEnum::STRIPE === $type && null !== $strypeRefence) {
  450.             try {
  451.                 $payment null;
  452.                 /** @var Payment $item */
  453.                 foreach ($invoice->getPayments()->toArray() as $item) {
  454.                     if ($item->getType() === $type) {
  455.                         $payment $item;
  456.                         break;
  457.                     }
  458.                 }
  459.                 if (null !== $payment) {
  460.                     $payment->setReference($strypeRefence);
  461.                     $payment->setStatus(PaymentStatusEnum::SUCCESS);
  462.                     $this->em->persist($payment);
  463.                 }
  464.                 $this->em->persist($invoice);
  465.                 $this->em->flush();
  466.                 return true;
  467.             } catch (\Exception $e) {
  468.                 $this->captureSentryException($e);
  469.                 $this->bus->dispatch(
  470.                     (new Envelope(new UpdateInvoiceReferenceStripeMessage($invoice->getId(), $strypeRefence)))->with(
  471.                         new AmazonSqsFifoStamp('Invoice'uniqid(''true))
  472.                     )
  473.                 );
  474.             }
  475.         }
  476.         return false;
  477.     }
  478.     public function unpaid(Invoice $invoice$type PaymentTypeEnum::STRIPE$strypeRefence null)
  479.     {
  480.         if (PaymentTypeEnum::STRIPE === $type) {
  481.             $payment null;
  482.             /** @var Payment $item */
  483.             foreach ($invoice->getPayments()->toArray() as $item) {
  484.                 if ($item->getType() === $type) {
  485.                     $payment $item;
  486.                     break;
  487.                 }
  488.             }
  489.             if (null !== $payment) {
  490.                 if ($strypeRefence) {
  491.                     $payment->setReference($strypeRefence);
  492.                 }
  493.                 $payment->setStatus(PaymentStatusEnum::ERROR);
  494.                 $this->em->persist($payment);
  495.                 $this->em->flush();
  496.                 return true;
  497.             }
  498.         }
  499.         return false;
  500.     }
  501.     /**
  502.      * @return bool
  503.      */
  504.     public function isInvoiceOfInscription(?Invoice $invoice)
  505.     {
  506.         if (!$invoice instanceof Invoice) {
  507.             return false;
  508.         }
  509.         $response false;
  510.         /** @var Item $item */
  511.         foreach ($invoice->getItems() as $item) {
  512.             /** @var Product $product */
  513.             $product $item->getProduct();
  514.             if (ProductKeyEnum::SECURITY_DEPOSIT === $product->getUniqueKey()) {
  515.                 $response true;
  516.                 break;
  517.             }
  518.         }
  519.         return $response;
  520.     }
  521.     public function isWaiting(Invoice $invoice): bool
  522.     {
  523.         $payments $invoice->getPayments()->toArray();
  524.         $isWaiting false;
  525.         /** @var Payment $payment */
  526.         foreach ($payments as $payment) {
  527.             if (PaymentTypeEnum::GOCARDLESS === $payment->getType() &&
  528.                 PaymentStatusEnum::WAITING === $payment->getStatus() &&
  529.                 in_array($invoice->getStatus(), [InvoiceStatusEnum::SENTInvoiceStatusEnum::LATE], true)) {
  530.                 $isWaiting true;
  531.                 break;
  532.             }
  533.             if (PaymentTypeEnum::STRIPE === $payment->getType() &&
  534.                 in_array($payment->getStatus(), [PaymentStatusEnum::WAITINGPaymentStatusEnum::ERROR], true) &&
  535.                 in_array($invoice->getStatus(), [InvoiceStatusEnum::SENTInvoiceStatusEnum::LATE], true)) {
  536.                 $isWaiting true;
  537.                 break;
  538.             }
  539.         }
  540.         return $isWaiting;
  541.     }
  542.     /**
  543.      * Check status of invoice to return true or false
  544.      * If status of invoice is unpaid the fields IsStatusInvoicePayed passed to true and function return false
  545.      * After the function checkt if payment is late or not.
  546.      *
  547.      * @return bool
  548.      */
  549.     public function checkUnpaid(Organization $organization)
  550.     {
  551.         $haveUnpaidInvoice false;
  552.         /** @var Invoice $invoice */
  553.         foreach ($organization->getInvoices() as $invoice) {
  554.             if (InvoiceStatusEnum::LATE === $invoice->getStatus() && !$invoice->getIsImported()) {
  555.                 $haveUnpaidInvoice true;
  556.                 break;
  557.             }
  558.         }
  559.         if (!$haveUnpaidInvoice) {
  560.             $organization->setStatusInvoicePayed(OrganizationStatusPayedEnum::PAID);
  561.             $this->em->flush();
  562.             return true;
  563.         }
  564.         $organization->setStatusInvoicePayed(OrganizationStatusPayedEnum::UNPAID);
  565.         $this->em->flush();
  566.         return false;
  567.     }
  568.     /**
  569.      * @return Invoice|bool|mixed
  570.      */
  571.     public function checkRegisterInvoice(Organization $organization)
  572.     {
  573.         foreach ($organization->getInvoices() as $invoice) {
  574.             foreach ($invoice->getItems() as $item) {
  575.                 if (ProductKeyEnum::SECURITY_DEPOSIT === $item->getProduct()->getUniqueKey()) {
  576.                     return $invoice;
  577.                 }
  578.             }
  579.         }
  580.         return false;
  581.     }
  582.     /**
  583.      * @return bool
  584.      */
  585.     public function isFullRepaid(Invoice $invoice)
  586.     {
  587.         $amoutCreditNote 0;
  588.         foreach ($invoice->getCreditNotes() as $creditNote) {
  589.             $amoutCreditNote += $creditNote->getAmount();
  590.         }
  591.         return $amoutCreditNote >= $invoice->getPrice();
  592.     }
  593.     public function sortByCreatedDate(&$array)
  594.     {
  595.         usort(
  596.             $array,
  597.             fn ($a$b) => $a->getCreatedAt() <=> $b->getCreatedAt()
  598.         );
  599.         $array array_reverse($array);
  600.     }
  601.     public function updateStatus(array $data): array
  602.     {
  603.         $invoiceRepo $this->em->getRepository(Invoice::class);
  604.         foreach ($data['invoices'] as $id) {
  605.             /** @var Invoice $invoice */
  606.             $invoice $this->em->getReference(Invoice::class, (int) $id);
  607.             if (!$invoice) {
  608.                 continue;
  609.             }
  610.             $payment $this->setNewStatusPayment($invoice$data);
  611.             $this->em->persist($payment);
  612.         }
  613.         $this->em->flush();
  614.         $invoices = [];
  615.         /* get invoices updated * */
  616.         foreach ($data['invoices'] as $id) {
  617.             /** @var Invoice $invoice */
  618.             $invoice $invoiceRepo->find((int) $id);
  619.             if (!$invoice) {
  620.                 continue;
  621.             }
  622.             $invoices[] = $invoice;
  623.         }
  624.         return $invoices;
  625.     }
  626.     private function setNewStatusPayment(Invoice $invoice, array $data): Payment
  627.     {
  628.         $payment $this->getPayment($invoicePaymentTypeEnum::STRIPE);
  629.         if (is_null($payment)) {
  630.             $payment = (new Payment())
  631.                 ->setType(PaymentTypeEnum::STRIPE)
  632.                 ->setInvoice($invoice)
  633.                 ->setAmount($invoice->getRemainingToPay());
  634.         }
  635.         if ('succeeded' === $data['stripe_status']) {
  636.             $payment->setStatus(PaymentStatusEnum::SUCCESS);
  637.         }
  638.         if ('pending' === $data['stripe_status']) {
  639.             $payment->setStatus(PaymentStatusEnum::WAITING);
  640.         }
  641.         return $payment;
  642.     }
  643.     public static function generateInvoiceUniquekey(int $subscriptionIdstring $subscriptionTypestring $yearMonth): string
  644.     {
  645.         $periodeKey PaymentRecurrenceEnum::MONTHLY === $subscriptionType 'M' 'A';
  646.         $periodeDate PaymentRecurrenceEnum::MONTHLY === $subscriptionType $yearMonth date('Y'strtotime($yearMonth));
  647.         return $subscriptionId.'-'.$periodeKey.'-'.$periodeDate;
  648.     }
  649.     public function createInvoice(
  650.         Organization $organization,
  651.         string $object,
  652.         float $amount,
  653.         float $globalDiscountWithoutTax,
  654.         array $items,
  655.         Payment $payment,
  656.         ?DiscountCode $promoCode
  657.     ): invoice {
  658.         $invoice = new Invoice();
  659.         if (null !== $promoCode) {
  660.             $invoice->setDiscountCode($promoCode);
  661.         }
  662.         $invoice->setObject($object);
  663.         if ($organization->isAttachedToPrescriber() && $organization->getPrescriber()) {
  664.             $invoice->setOrganization($organization->getPrescriber());
  665.         } else {
  666.             $invoice->setOrganization($organization);
  667.         }
  668.         $invoice->setIsSent(true);
  669.         $invoice->setStatus(InvoiceStatusEnum::PAID);
  670.         $invoice->setPrice($amount);
  671.         $invoice->setDiscountWithoutTax($globalDiscountWithoutTax);
  672.         foreach ($items as $item) {
  673.             $invoice->addItem($item);
  674.         }
  675.         $invoice->addPayment($payment);
  676.         $this->em->persist($invoice);
  677.         return $invoice;
  678.     }
  679.     public static function setPrices(Invoice $invoice): void
  680.     {
  681.         if (=== count($invoice->getItems())) {
  682.             throw new \LogicException('This invoice must have minimum one item !');
  683.         }
  684.         if (null !== $invoice->getVATRate()) {
  685.             $totalPriceWithoutTax 0;
  686.             foreach ($invoice->getItems() as $item) {
  687.                 if (!$item->getIsOffer()) {
  688.                     $sum = (($item->getUnitPriceWithoutTax() * $item->getQuantity()) - $item->getDiscountWithoutTax());
  689.                     if ($sum 0) {
  690.                         $sum 0;
  691.                     }
  692.                     $totalPriceWithoutTax += $sum;
  693.                 }
  694.             }
  695.             if (!$invoice->getOrganization() && !$invoice->getIsTemplate() && !$invoice->isModel()) {
  696.                 throw new \LogicException('This invoice must have a organization !');
  697.             }
  698.             if (null !== $invoice->getOrganization()) {
  699.                 $isExemptTVA $invoice->getOrganization()->getIsExemptTVA();
  700.                 if (true === $isExemptTVA) {
  701.                     $invoice->setVATRate(VATRateEnum::VAT_0);
  702.                 }
  703.             }
  704.             $VATRate $invoice->getVATRate();
  705.             $totalPriceWithoutTax -= $invoice->getDiscountWithoutTax();
  706.             $price round($totalPriceWithoutTax * ($VATRate 100), 2);
  707.             if ($price 0) {
  708.                 $price 0;
  709.             }
  710.             $priceWithoutTax round($price / ($VATRate 100), 2);
  711.             $invoice->setPrice($price);
  712.             $invoice->setPriceWithoutTax($priceWithoutTax);
  713.         }
  714.     }
  715.     public function generateNumberForInvoice(Invoice $invoice): void
  716.     {
  717.         $this->client->select(0);
  718.         $lockKey 'invoice_lock';
  719.         if (!$invoice->getNumber() && !$invoice->getIsImported()) {
  720.             $lockAcquired $this->client->set($lockKey1'EX'5'NX');
  721.             if ($lockAcquired) {
  722.                 try {
  723.                     $this->em->beginTransaction();
  724.                     $lastInvoice $this->invoiceRepository->getLastNumber();
  725.                     $currentInvoiceNumber $this->client->get('invoice_number');
  726.                     if (!$currentInvoiceNumber || $lastInvoice $currentInvoiceNumber) {
  727.                         $this->client->set('invoice_number'$lastInvoice);
  728.                     }
  729.                     $newInvoiceNumber $this->client->incr('invoice_number');
  730.                     $invoice->setNumber($newInvoiceNumber);
  731.                     $this->em->persist($invoice);
  732.                     $this->em->flush();
  733.                     $this->em->commit();
  734.                 } catch (\Exception $e) {
  735.                     $this->em->rollback();
  736.                     $this->client->decr('invoice_number');
  737.                     throw $e;
  738.                 } finally {
  739.                     $this->client->del($lockKey);
  740.                 }
  741.             } else {
  742.                 throw new \RuntimeException('Cannot acquire lock for invoice number generation, please try again.');
  743.             }
  744.         }
  745.     }
  746. }