src/Utils/InvoiceUtils.php line 893

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