src/Controller/Api/InvoiceController.php line 185

Open in your IDE?
  1. <?php
  2. namespace App\Controller\Api;
  3. use App\CustomInterface\SubscriptionUtilsInterface;
  4. use App\Enum\DiscountCodeTypeEnum;
  5. use App\Enum\InvoiceStatusEnum;
  6. use App\Enum\LegalStatusTypeEnum;
  7. use App\Enum\OrderCategoriesEnum;
  8. use App\Enum\OrderStatusEnum;
  9. use App\Enum\OrganizationStatusEnum;
  10. use App\Enum\OrganizationStatusImmatriculationEnum;
  11. use App\Enum\OrganizationStatusTransfertEnum;
  12. use App\Enum\PaymentRecurrenceEnum;
  13. use App\Enum\PaymentStatusEnum;
  14. use App\Enum\PaymentTypeEnum;
  15. use App\Enum\ProductKeyEnum;
  16. use App\Enum\SubscriptionPackEnum;
  17. use App\Repository\OrderRepository;
  18. use App\Repository\SubscriptionRepository;
  19. use App\Service\EncryptorDataUtils;
  20. use App\Service\InvoiceFilesService;
  21. use App\Service\Invoices\InvoiceService;
  22. use App\Service\SegmentAPI;
  23. use App\Service\SubscriptionUtils;
  24. use App\Traits\SentryNotifyTrait;
  25. use App\Utils\InvoiceUtils;
  26. use App\Utils\OrderUtils;
  27. use Doctrine\ORM\EntityManagerInterface;
  28. use Evo\Infrastructure\Mapper\Invoice\InvoiceMapper;
  29. use Evo\Infrastructure\Mapper\Organization\OrganizationMapper;
  30. use Evo\Infrastructure\MappingORM\DiscountCode;
  31. use Evo\Infrastructure\MappingORM\Invoice;
  32. use Evo\Infrastructure\MappingORM\Item;
  33. use Evo\Infrastructure\MappingORM\Order;
  34. use Evo\Infrastructure\MappingORM\Organization;
  35. use Evo\Infrastructure\MappingORM\Payment;
  36. use Evo\Infrastructure\MappingORM\Product;
  37. use Evo\Infrastructure\MappingORM\Subscription;
  38. use Evo\Infrastructure\Repository\InvoiceRepository;
  39. use Knp\Snappy\Pdf;
  40. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  41. use Symfony\Component\HttpFoundation\JsonResponse;
  42. use Symfony\Component\HttpFoundation\Request;
  43. use Symfony\Component\HttpFoundation\Response;
  44. use Symfony\Component\HttpKernel\KernelInterface;
  45. use Symfony\Component\Messenger\MessageBusInterface;
  46. use Symfony\Component\Routing\Annotation\Route;
  47. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  48. use Symfony\Component\Security\Core\Security;
  49. use SymfonyBundles\RedisBundle\Redis\ClientInterface;
  50. use Twig\Environment;
  51. /**
  52.  * @Route("/invoice")
  53.  */
  54. class InvoiceController extends AbstractController
  55. {
  56.     use SentryNotifyTrait;
  57.     public const SERVICE_NAME '[INVOICE_CONTROLLER] :: ';
  58.     private EntityManagerInterface $em;
  59.     private SegmentAPI $segmentAPI;
  60.     private SubscriptionUtilsInterface $subscriptionUtils;
  61.     private EncryptorDataUtils $encryptor;
  62.     private InvoiceUtils $invoiceUtils;
  63.     private InvoiceRepository $invoiceRepository;
  64.     private Environment $twig;
  65.     private Pdf $pdf;
  66.     private InvoiceFilesService $invoiceFilesService;
  67.     private KernelInterface $kernel;
  68.     private UrlGeneratorInterface $router;
  69.     private OrderUtils $orderUtils;
  70.     private ClientInterface $client;
  71.     private MessageBusInterface $bus;
  72.     private InvoiceService $invoiceService;
  73.     public function __construct(
  74.         EntityManagerInterface $em,
  75.         InvoiceFilesService $invoiceFilesService,
  76.         EncryptorDataUtils $encryptor,
  77.         InvoiceUtils $invoiceUtils,
  78.         OrderUtils $orderUtils,
  79.         SubscriptionUtilsInterface $subscriptionUtils,
  80.         SegmentAPI $segmentAPI,
  81.         InvoiceRepository $invoiceRepository,
  82.         Environment $twig,
  83.         Pdf $pdf,
  84.         KernelInterface $kernel,
  85.         UrlGeneratorInterface $router,
  86.         ClientInterface $client,
  87.         MessageBusInterface $bus,
  88.         InvoiceService $invoiceService
  89.     ) {
  90.         $this->em $em;
  91.         $this->encryptor $encryptor;
  92.         $this->segmentAPI $segmentAPI;
  93.         $this->invoiceUtils $invoiceUtils;
  94.         $this->subscriptionUtils $subscriptionUtils;
  95.         $this->orderUtils $orderUtils;
  96.         $this->invoiceRepository $invoiceRepository;
  97.         $this->twig $twig;
  98.         $this->pdf $pdf;
  99.         $this->invoiceFilesService $invoiceFilesService;
  100.         $this->kernel $kernel;
  101.         $this->router $router;
  102.         $this->client $client;
  103.         $this->bus $bus;
  104.         $this->invoiceService $invoiceService;
  105.     }
  106.     /**
  107.      * @Route("/view/{id}/{token}", name="app_invoice_view", methods={"GET"})
  108.      *
  109.      * @param null $token
  110.      *
  111.      * @return JsonResponse
  112.      */
  113.     public function viewInvoice($id$token null)
  114.     {
  115.         $repository $this->em->getRepository(Invoice::class);
  116.         $repositoryOrganization $this->em->getRepository(Organization::class);
  117.         /** @var Organization $_organization */
  118.         $_organization $repositoryOrganization->findOneBy(['invoiceToken' => $token]);
  119.         $id $this->encryptor->decrypt($id);
  120.         /** @var Invoice $invoice */
  121.         $invoice $repository->find($id);
  122.         if (!$_organization || !$invoice) {
  123.             return $this->json(['result' => 'Invoice not found'], Response::HTTP_NOT_FOUND);
  124.         }
  125.         $organization $invoice->getOrganization();
  126.         if (null !== $organization && $organization->getId() === $_organization->getId()) {
  127.             $this->invoiceFilesService->viewFile($invoice->getPath());
  128.         }
  129.         return $this->json(['result' => 'Document not found'], Response::HTTP_NOT_FOUND);
  130.     }
  131.     /**
  132.      * @Route("/product-category/{id}", name="app_invoice_product_category", methods={"GET"})
  133.      */
  134.     public function getInvoiceProductCategory(int $id): JsonResponse
  135.     {
  136.         /** @var Invoice $invoice */
  137.         $invoice $this->invoiceRepository->find($id);
  138.         $productCategories = [];
  139.         $items = [];
  140.         foreach ($invoice->getItems() as $item) {
  141.             $product $item->getProduct();
  142.             if (null !== $product && null !== $product->getCategory()) {
  143.                 $items[] = [
  144.                     'name' => $product->getName(),
  145.                     'price' => $product->getPriceWithoutTax(),
  146.                     'product_id' => $product->getId(),
  147.                     'quantity' => $item->getQuantity(),
  148.                 ];
  149.                 $productCategories[] = (string) $product->getCategory()->getType();
  150.             }
  151.         }
  152.         $category $this->getInvoiceProductCategoryTitle($productCategories);
  153.         return new JsonResponse(['category' => $category'items' => $items], Response::HTTP_OK);
  154.     }
  155.     /**
  156.      * @param array<string> $ACategories
  157.      */
  158.     private function getInvoiceProductCategoryTitle(array $ACategories): string
  159.     {
  160.         $occurrences array_count_values($ACategories);
  161.         return (string) array_search(max($occurrences), $occurrences);
  162.     }
  163.     /**
  164.      * @Route("/remaining-to-pay/{organizationId}", name="app_invoice_remaining_to_pay", methods={"GET"})
  165.      */
  166.     public function getRemainingToPay(int $organizationId): JsonResponse
  167.     {
  168.         /** @var InvoiceRepository $repository */
  169.         $repository $this->em->getRepository(Invoice::class);
  170.         $invoices $repository->findBy(['organization' => $organizationId]);
  171.         $remainingToPay 0;
  172.         foreach ($invoices as $invoice) {
  173.             $creditNoteAmount 0;
  174.             $isPaidOrOverPaid in_array($invoice->getStatus(), [InvoiceStatusEnum::OVER_PAIDInvoiceStatusEnum::PAID]);
  175.             $isWaiting in_array($invoice->getStatus(), [InvoiceStatusEnum::SENTInvoiceStatusEnum::LATEInvoiceStatusEnum::PARTIALLY_PAID]) &&
  176.                 $this->invoiceUtils->isHavingWaitingPayment($invoice);
  177.             if (!$isPaidOrOverPaid && !$isWaiting) {
  178.                 if ($invoice->getCreditNotes()) {
  179.                     foreach ($invoice->getCreditNotes() as $creditNote) {
  180.                         $creditNoteAmount += $creditNote->getAmount();
  181.                     }
  182.                 }
  183.                 $remainingToPay = ($remainingToPay + ($invoice->getPrice() - $invoice->getTotalPaid())) - $creditNoteAmount;
  184.             }
  185.         }
  186.         return new JsonResponse(
  187.             [
  188.                 'remainingToPay' => $remainingToPay,
  189.             ],
  190.             Response::HTTP_OK
  191.         );
  192.     }
  193.     /**
  194.      * @Route("/pending-payment/{organizationId}", name="app_invoice_pending_payment", methods={"GET"})
  195.      */
  196.     public function getInvoiceWithPendingPayment(int $organizationId): JsonResponse
  197.     {
  198.         /** @var InvoiceRepository $repository */
  199.         $repository $this->em->getRepository(Invoice::class);
  200.         $invoices $repository->findBy(['organization' => $organizationId]);
  201.         $pendingAmount 0;
  202.         foreach ($invoices as $invoice) {
  203.             $isWaiting in_array($invoice->getStatus(), [
  204.                     InvoiceStatusEnum::SENT,
  205.                     InvoiceStatusEnum::LATE,
  206.                     InvoiceStatusEnum::PARTIALLY_PAID,
  207.                 ]) &&
  208.                 $this->invoiceUtils->isHavingWaitingPayment($invoice);
  209.             if ($isWaiting) {
  210.                 $pendingAmount += $this->invoiceUtils->setTotalWaitingPayment($invoice);
  211.             }
  212.         }
  213.         return new JsonResponse(
  214.             [
  215.                 'pendingAmount' => $pendingAmount,
  216.             ],
  217.             Response::HTTP_OK
  218.         );
  219.     }
  220.     /**
  221.      * @Route("/download/{id}", name="app_invoice_download", methods={"GET"})
  222.      */
  223.     public function downloadAWSFile($id, ?bool $sendLink false)
  224.     {
  225.         $repository $this->em->getRepository(Invoice::class);
  226.         $id $this->encryptor->decrypt($id);
  227.         $invoice $repository->find($id);
  228.         if (null !== $invoice) {
  229.             $this->invoiceFilesService->downloadInvoice($invoice->getPath(), $sendLink);
  230.         }
  231.         return $this->json(['result' => 'Invoice not found'], Response::HTTP_NOT_FOUND);
  232.     }
  233.     /**
  234.      * @Route("/paid", name="app_invoice_paid", methods={"POST", "PUT"})
  235.      */
  236.     public function invoicePaid(): JsonResponse
  237.     {
  238.         return $this->json(['message' => 'success']);
  239.     }
  240.     /**
  241.      * @Route("/check-duplicated-invoice", name="app_invoice_check_duplicated", methods={"GET"})
  242.      */
  243.     public function checkDuplicatedInvoice(): JsonResponse
  244.     {
  245.         $duplicatedInvoices $this->invoiceRepository->getDuplicatedInvoice(nulltrue);
  246.         $isHavingDuplicatedInvoice false;
  247.         if ((is_countable($duplicatedInvoices) ? count($duplicatedInvoices) : 0) > 0) {
  248.             $isHavingDuplicatedInvoice true;
  249.         }
  250.         return new JsonResponse(['isHavingDuplicatedInvoice' => $isHavingDuplicatedInvoice], Response::HTTP_OK);
  251.     }
  252.     /**
  253.      * @Route("/generate", name="app_generate_invoice", methods={"POST"})
  254.      *
  255.      * @throws \JsonException
  256.      * @throws \Exception
  257.      */
  258.     public function invoice(Request $request): JsonResponse
  259.     {
  260.         $query json_decode($request->getContent(), true512JSON_THROW_ON_ERROR);
  261.         // get query params
  262.         $organizationID $query['organization'];
  263.         $typePayment $query['type_payment'];
  264.         $object $query['object'];
  265.         $amount $query['amount'];
  266.         $pack $query['pack'] ?? null;
  267.         $category $query['category'] ?? null;
  268.         $reference $query['reference'] ?? null;
  269.         $promoCode $query['promo_code'] ?? null;
  270.         $globalDiscountWithoutTax 0;
  271.         $discountCode null;
  272.         $organization $this->getOrganization((int) $organizationID);
  273.         /** @var Subscription $subscription */
  274.         $subscription SubscriptionUtils::getDomSubscription($organization);
  275.         if (isset($promoCode)) {
  276.             $productRepository $this->em->getRepository(Product::class);
  277.             $packProduct $productRepository->findOneBy(['uniqueKey' => $pack]);
  278.             $discountCode $this->getPromoCode($promoCode$amount$packProduct);
  279.         }
  280.         if ($discountCode && DiscountCodeTypeEnum::INVOICE_DISCOUNT === $discountCode->getType()) {
  281.             $globalDiscountWithoutTax = (float) $discountCode->getFirstDiscountConfiguration()->getAmount();
  282.         }
  283.         // get items for subscription
  284.         $items = [];
  285.         switch ($category) {
  286.             case 'DOMICILIATION':
  287.                 $items $this->generateDomItem($pack$subscription$discountCode);
  288.                 break;
  289.             case 'IMMATRICULATION':
  290.                 $items $this->generateImmatItems($pack$discountCode);
  291.                 break;
  292.             case 'DOM_IMMAT':
  293.             case 'DOM_TRANSFERT':
  294.                 $items $this->generateDomImmatItems($pack$organization$category$subscription$discountCode);
  295.                 break;
  296.         }
  297.         // create payment
  298.         $payment $this->createPayment($typePayment$amount$reference$globalDiscountWithoutTax$discountCode$category$items);
  299.         // create invoice
  300.         $invoice $this->invoiceUtils->createInvoice($organization$object$amount $globalDiscountWithoutTax$globalDiscountWithoutTax$items$payment$discountCode);
  301.         $this->em->flush();
  302.         if ($discountCode && DiscountCodeTypeEnum::SUBSCRIPTION_DISCOUNT === $discountCode->getType()) {
  303.             $this->addDiscountToSubscription($organization, (string) $discountCode->getFirstDiscountConfiguration()->getAmount());
  304.         }
  305.         $organization $this->updateStatusOrganization($organization);
  306.         $this->em->persist($organization);
  307.         if ($pack) {
  308.             if ('IMMATRICULATION' === $category) {
  309.                 $this->updateImmatSubscription($pack$organization$subscription);
  310.             } else {
  311.                 $this->updateDomNextPayment($organization$subscription);
  312.             }
  313.         }
  314.         // segment track
  315.         $this->sendIdentityToSegmentAPI($organization);
  316.         $this->segmentAPI->trackNewOrder($invoice);
  317.         // flush  all
  318.         $this->em->flush();
  319.         $data = [
  320.             'id' => $organization->getId(),
  321.             'status' => $organization->getStatus(),
  322.             'invoice' => $invoice->getId(),
  323.         ];
  324.         return new JsonResponse($dataResponse::HTTP_CREATED);
  325.     }
  326.     /**
  327.      * @Route("/create-first-invoice", name="app_create_first_invoice", methods={"POST"})
  328.      *
  329.      * @throws \JsonException
  330.      * @throws \Exception
  331.      */
  332.     public function createFirstInvoice(Request $request): JsonResponse
  333.     {
  334.         $query json_decode($request->getContent(), true512JSON_THROW_ON_ERROR);
  335.         $organizationID = (int) $query['organization'];
  336.         $queryItems $query['products'] ?? [];
  337.         // get organization and subscription
  338.         $organization $this->getOrganization($organizationID);
  339.         $subscription SubscriptionUtils::getDomSubscription($organization);
  340.         // set up items
  341.         $items = [];
  342.         $productRepository $this->em->getRepository(Product::class);
  343.         foreach ($queryItems as $queryItem) {
  344.             $product $productRepository->findOneBy(['uniqueKey' => $queryItem['uniqueKey']]);
  345.             $items[] = $this->setUpItems($queryItem$product);
  346.         }
  347.         // create payment
  348.         $paymentType $query['type_payment'];
  349.         $paymentAmount = (float) $query['amount'];
  350.         $paymentReference $query['reference'];
  351.         $payment $this->setUpFirstPayment($paymentType$paymentAmount$paymentReference);
  352.         // create invoice
  353.         $invoiceAmount = (float) $query['amount'];
  354.         $invoiceObject $query['object'];
  355.         $discountCode null;
  356.         if (null !== $query['promoCodeId']) {
  357.             $discountCodeRepository $this->em->getRepository(DiscountCode::class);
  358.             $discountCode $discountCodeRepository->findOneBy(['id' => $query['promoCodeId']]);
  359.         }
  360.         $invoiceGlobalDiscountWithoutTax = (float) $query['discount'];
  361.         $invoice $this->setUpFirstInvoice($organization$invoiceObject$invoiceAmount$invoiceGlobalDiscountWithoutTax$items$payment$discountCode);
  362.         // flush invoice
  363.         $this->em->flush();
  364.         if ($discountCode && DiscountCodeTypeEnum::SUBSCRIPTION_DISCOUNT === $discountCode->getType()) {
  365.             $this->addDiscountToSubscription($organization$query['discount']);
  366.         }
  367.         $organization $this->updateStatusOrganization($organization);
  368.         $this->em->persist($organization);
  369.         $pack $query['pack'];
  370.         $category $query['category'];
  371.         /* Update prepaid months by discount code before updateImmatSubscription & updateDomNextPayment */
  372.         $this->subscriptionUtils->updatePrepaidMonthsByDiscountCode($subscription$discountCode);
  373.         // update period start & end date
  374.         $this->invoiceService->updatePeriodStartAndEnd($invoice$discountCode);
  375.         $subscriptionRepository $this->em->getRepository(Subscription::class);
  376.         if ($subscription && PaymentRecurrenceEnum::ANNUAL === $subscription->getType()) {
  377.             $domStart $organization->getDomiciliationStartDate();
  378.             if ($domStart) {
  379.                 $subscriptionRepository->setNextPayment($subscription->getId(), $domStart->modify('+1 year'));
  380.             } else {
  381.                 $subscriptionRepository->setNextPayment($subscription->getId(), new \DateTime('+1 year'));
  382.             }
  383.         }
  384.         if ($pack) {
  385.             if ('IMMATRICULATION' === $category) {
  386.                 $this->updateImmatSubscription($pack$organization$subscription);
  387.             } else {
  388.                 $this->updateDomNextPayment($organization$subscription);
  389.             }
  390.         }
  391.         // segment track
  392.         $this->sendIdentityToSegmentAPI($organization);
  393.         $this->segmentAPI->trackNewOrder($invoice);
  394.         // flush  all
  395.         $this->em->flush();
  396.         $returnData = [
  397.             'id' => $organization->getId(),
  398.             'status' => $organization->getStatus(),
  399.             'invoice' => $invoice->getId(),
  400.         ];
  401.         return new JsonResponse($returnDataResponse::HTTP_CREATED);
  402.     }
  403.     private function setUpItems(array $queryItemProduct $product): Item
  404.     {
  405.         $item = new Item();
  406.         $item->setProduct($product);
  407.         $item->setUnitPriceWithoutTax((float) $queryItem['priceWithoutTax']);
  408.         $item->setQuantity((int) $queryItem['quantity']);
  409.         $item->setDiscountWithoutTax((float) $queryItem['discount']);
  410.         return $item;
  411.     }
  412.     private function setUpFirstPayment(string $paymentTypefloat $paymentAmountstring $paymentReference): Payment
  413.     {
  414.         $payment = new Payment();
  415.         $payment->setType($paymentType);
  416.         $payment->setAmount($paymentAmount);
  417.         $payment->setStatus(PaymentStatusEnum::SUCCESS);
  418.         $payment->setReference($paymentReference);
  419.         return $payment;
  420.     }
  421.     private function setUpFirstInvoice(
  422.         Organization $organization,
  423.         string $object,
  424.         float $amount,
  425.         float $globalDiscountWithoutTax,
  426.         array $items,
  427.         Payment $payment,
  428.         ?DiscountCode $discountCode
  429.     ): Invoice {
  430.         $invoice = new Invoice();
  431.         $invoice->setObject($object);
  432.         $invoice->setIsSent(true);
  433.         $invoice->setStatus(InvoiceStatusEnum::PAID);
  434.         $invoice->setPrice($amount);
  435.         if ($organization->isAttachedToPrescriber() && $organization->getPrescriber()) {
  436.             $invoice->setOrganization($organization->getPrescriber());
  437.         } else {
  438.             $invoice->setOrganization($organization);
  439.         }
  440.         foreach ($items as $item) {
  441.             $invoice->addItem($item);
  442.         }
  443.         if (null !== $discountCode) {
  444.             $invoice->setDiscountCode($discountCode);
  445.             if (DiscountCodeTypeEnum::INVOICE_DISCOUNT === $discountCode->getType()) {
  446.                 $invoice->setDiscountWithoutTax($globalDiscountWithoutTax);
  447.             } else {
  448.                 $invoice->setDiscountWithoutTax(0);
  449.             }
  450.         } else {
  451.             $invoice->setDiscountWithoutTax($globalDiscountWithoutTax);
  452.         }
  453.         $invoice->addPayment($payment);
  454.         $this->em->persist($invoice);
  455.         return $invoice;
  456.     }
  457.     private function getPromoCode(int $promoCodefloat $amount, ?Product $product): ?DiscountCode
  458.     {
  459.         $repository $this->em->getRepository(DiscountCode::class);
  460.         /** @var ?DiscountCode $promoCode */
  461.         $promoCode $repository->findOneBy(['id' => $promoCode]);
  462.         if (null !== $promoCode) {
  463.             $discountAmount $this->invoiceUtils->discountCodeCalculation($amount$promoCode);
  464.             if (DiscountCodeTypeEnum::PRODUCT_DISCOUNT !== $promoCode->getType()) {
  465.                 $promoCode->getFirstDiscountConfiguration()->setAmount($discountAmount);
  466.             } elseif (null !== $product) {
  467.                 $discountConfiguration $promoCode->findDiscountProductTarget($product);
  468.                 if (null !== $discountConfiguration) {
  469.                     $discountConfiguration->setAmount($discountAmount);
  470.                 }
  471.             }
  472.             return $promoCode;
  473.         }
  474.         return null;
  475.     }
  476.     /**
  477.      * @throws \Exception
  478.      */
  479.     private function updateImmatSubscription(string $packOrganization $organization, ?Subscription $subscription): void
  480.     {
  481.         $this->updateStatusOrderImmat();
  482.         if (in_array($pack, [SubscriptionPackEnum::CONFORT_MICROSubscriptionPackEnum::CONFORT]) &&
  483.             LegalStatusTypeEnum::AUTO_ENTREPRENEUR === $organization->getLegalStatus()
  484.         ) {
  485.             $domDate $organization->getDomiciliationStartDate();
  486.             if ($domDate && $subscription) {
  487.                 $domicilingStartDate $domDate->getTimestamp();
  488.                 $frequency SubscriptionUtils::getFrequency($subscription);
  489.                 $nextPaymentDate date('Y-m-d', (int) strtotime($frequency$domicilingStartDate));
  490.                 $subscription->setNextPayment(new \DateTime($nextPaymentDate));
  491.                 $this->em->persist($subscription);
  492.                 $this->em->flush();
  493.             }
  494.         }
  495.     }
  496.     private function updateDomNextPayment(Organization $organization, ?Subscription $subscription): void
  497.     {
  498.         /** @var SubscriptionRepository $subscriptionRepository */
  499.         $subscriptionRepository $this->em->getRepository(Subscription::class);
  500.         $nextPaymentDate null;
  501.         $domDate $organization->getDomiciliationStartDate();
  502.         if ($domDate && $subscription) {
  503.             $domicilingStartDate $domDate->getTimestamp();
  504.             $frequency SubscriptionUtils::getFrequency($subscription);
  505.             $nextPaymentDate date('Y-m-d', (int) strtotime($frequency$domicilingStartDate));
  506.             $subscriptionID $subscription->getId();
  507.             $subscriptionRepository->setNextPayment($subscriptionID$nextPaymentDate);
  508.         }
  509.     }
  510.     private function createPayment(string $typePaymentfloat $amountstring $referencefloat $globalDiscount, ?DiscountCode $discountCodestring $category, array $items): Payment
  511.     {
  512.         $finalAmount $amount;
  513.         if ($discountCode &&
  514.             in_array($discountCode->getType(), [
  515.                 DiscountCodeTypeEnum::SUBSCRIPTION_DISCOUNT,
  516.                 DiscountCodeTypeEnum::PRODUCT_DISCOUNT,
  517.             ], true)
  518.         ) {
  519.             $countProductWIthDiscount 1;
  520.             $itemsWithDiscount = [];
  521.             if (DiscountCodeTypeEnum::PRODUCT_DISCOUNT === $discountCode->getType() &&
  522.                 in_array($category, ['IMMATRICULATION''DOM_IMMAT''DOM_TRANSFERT'])
  523.             ) {
  524.                 /** @var Item $item */
  525.                 foreach ($items as $item) {
  526.                     if ($item->getDiscountWithoutTax() > 0) {
  527.                         $itemsWithDiscount[] = $item;
  528.                     }
  529.                 }
  530.                 $countProductWIthDiscount count($itemsWithDiscount);
  531.             }
  532.             $withDiscountPrice $finalAmount - ($discountCode->getFirstDiscountConfiguration()->getAmount() * $countProductWIthDiscount);
  533.             $finalAmount = ($withDiscountPrice + ($withDiscountPrice 0.2));
  534.         }
  535.         if (!$discountCode && $globalDiscount <= 0) {
  536.             $finalAmount += $finalAmount 0.2;
  537.         }
  538.         if ($globalDiscount 0) {
  539.             $finalAmount = ($finalAmount $globalDiscount) + (($finalAmount $globalDiscount) * 0.2);
  540.         }
  541.         $payment = new Payment();
  542.         $payment->setType($typePayment);
  543.         $payment->setAmount($finalAmount);
  544.         $payment->setStatus(PaymentStatusEnum::SUCCESS);
  545.         $payment->setReference($reference);
  546.         return $payment;
  547.     }
  548.     /**
  549.      * @return array<Item>
  550.      */
  551.     private function generateDomItem(string $packSubscription $subscription, ?DiscountCode $discountCode): array
  552.     {
  553.         $productRepository $this->em->getRepository(Product::class);
  554.         // pack product
  555.         /** @var Product $packProduct */
  556.         $packProduct $productRepository->findOneBy(['uniqueKey' => $pack]);
  557.         $items = [];
  558.         // change if needed to change quantity
  559.         $packItem $this->hydrateItem($packProduct, (float) $subscription->getPriceWithoutTax(), 1$discountCode);
  560.         $this->em->persist($packItem);
  561.         // Security deposit product
  562.         $depositSecurityProduct $productRepository->findOneBy(['uniqueKey' => ProductKeyEnum::SECURITY_DEPOSIT]);
  563.         $depositItem $this->hydrateItem($depositSecurityProduct01null);
  564.         $this->em->persist($depositItem);
  565.         $items[] = $packItem;
  566.         $items[] = $depositItem;
  567.         return $items;
  568.     }
  569.     /**
  570.      * @return array<Item>
  571.      */
  572.     private function generateImmatItems(string $pack, ?DiscountCode $discountCode): array
  573.     {
  574.         $items = [];
  575.         $packProducts SubscriptionPackEnum::IMMAT_PACK[$pack];
  576.         $discountProductKeys = [];
  577.         if ($discountCode &&
  578.             DiscountCodeTypeEnum::PRODUCT_DISCOUNT === $discountCode->getType()
  579.         ) {
  580.             $discountConfigurations $discountCode->getDiscountConfigurations();
  581.             foreach ($discountConfigurations as $discountConfiguration) {
  582.                 $targetProducts $discountConfiguration->getTargetProducts();
  583.                 foreach ($targetProducts as $targetProduct) {
  584.                     $discountProductKeys[] = $targetProduct->getUniqueKey();
  585.                 }
  586.             }
  587.         }
  588.         foreach ($packProducts as $packProduct) {
  589.             $isNeedDiscount in_array($packProduct$discountProductKeystrue);
  590.             $product $this->em->getRepository(Product::class)->findOneBy(['uniqueKey' => $packProduct]);
  591.             if (null === $product) {
  592.                 throw new \RuntimeException('Product not found');
  593.             }
  594.             $item $this->hydrateItem($product, (float) $product->getPriceWithoutTax(), 1$isNeedDiscount $discountCode null);
  595.             $items[] = $item;
  596.         }
  597.         return $items;
  598.     }
  599.     /**
  600.      * @return array<Item>
  601.      */
  602.     private function generateDomImmatItems(
  603.         string $pack,
  604.         Organization $organization,
  605.         string $category,
  606.         Subscription $subscription,
  607.         ?DiscountCode $discountCode
  608.     ): array {
  609.         $orders $organization->getOrders()->getValues();
  610.         $immatItems $this->orderUtils->getItemsByOrderCategory($orders$category);
  611.         $discountProductKeys = [];
  612.         if ($discountCode &&
  613.             DiscountCodeTypeEnum::PRODUCT_DISCOUNT === $discountCode->getType()
  614.         ) {
  615.             $discountConfigurations $discountCode->getDiscountConfigurations();
  616.             foreach ($discountConfigurations as $discountConfiguration) {
  617.                 $targetProducts $discountConfiguration->getTargetProducts();
  618.                 foreach ($targetProducts as $targetProduct) {
  619.                     $discountProductKeys[] = $targetProduct->getUniqueKey();
  620.                 }
  621.             }
  622.         }
  623.         /** @var Item $item */
  624.         foreach ($immatItems as $item) {
  625.             $uniqueKey $item->getProduct()->getUniqueKey();
  626.             if ($uniqueKey &&
  627.                 in_array($uniqueKey$discountProductKeystrue)) {
  628.                 if ($discountCode && DiscountCodeTypeEnum::SUBSCRIPTION_DISCOUNT === $discountCode->getType()) {
  629.                     $item->setDiscountWithoutTax($discountCode->getFirstDiscountConfiguration()->getAmount());
  630.                 }
  631.                 if ($discountCode && DiscountCodeTypeEnum::PRODUCT_DISCOUNT === $discountCode->getType()) {
  632.                     $discountConfiguration $discountCode->findDiscountProductTarget($item->getProduct());
  633.                     if ($discountConfiguration) {
  634.                         $item->setDiscountWithoutTax($discountConfiguration->getAmount());
  635.                     }
  636.                 }
  637.             }
  638.         }
  639.         $domItems $this->generateDomItem($pack$subscription$discountCode);
  640.         return array_merge_recursive($immatItems$domItems);
  641.     }
  642.     /**
  643.      * @throws \Exception
  644.      */
  645.     private function getOrganization(int $organizationID): Organization
  646.     {
  647.         $repositoryOrganization $this->em->getRepository(Organization::class);
  648.         /** @var ?Organization $organization */
  649.         $organization $repositoryOrganization->find($organizationID);
  650.         if (is_null($organization)) {
  651.             throw new \Exception('Organization not found');
  652.         }
  653.         return $organization;
  654.     }
  655.     private function hydrateItem(Product $productfloat $priceWithoutTaxint $quantity, ?DiscountCode $discountCode): Item
  656.     {
  657.         $item = new Item();
  658.         $item->setProduct($product);
  659.         $item->setUnitPriceWithoutTax($priceWithoutTax);
  660.         $item->setQuantity($quantity);
  661.         if ($discountCode && DiscountCodeTypeEnum::SUBSCRIPTION_DISCOUNT === $discountCode->getType()) {
  662.             $item->setDiscountWithoutTax($discountCode->getFirstDiscountConfiguration()->getAmount());
  663.         }
  664.         if ($discountCode && DiscountCodeTypeEnum::PRODUCT_DISCOUNT === $discountCode->getType()) {
  665.             $discountForProduct $discountCode->findDiscountProductTarget($product);
  666.             if ($discountForProduct) {
  667.                 $item->setDiscountWithoutTax($discountForProduct->getAmount());
  668.             }
  669.         }
  670.         return $item;
  671.     }
  672.     private function sendIdentityToSegmentAPI(Organization $organization): void
  673.     {
  674.         if ([] !== $organization->getUsers()) {
  675.             $firstUser $organization->getUsers()[0];
  676.             $this->segmentAPI->identify([
  677.                 'userId' => $organization->getUniqId(),
  678.                 'traits' => [
  679.                     'id_user' => $firstUser->getUniqId(),
  680.                     'id_organization' => $organization->getUniqId(),
  681.                     'token' => $organization->getInvoiceToken(),
  682.                 ],
  683.             ]);
  684.         }
  685.     }
  686.     private function updateStatusOrderImmat(): void
  687.     {
  688.         /** @var OrderRepository $orderRepository */
  689.         $orderRepository $this->em->getRepository(Order::class);
  690.         /** @var Order $orderImmatriculation */
  691.         $orderImmatriculation $orderRepository->findOneBy(['category' => OrderCategoriesEnum::IMMATRICULATION]);
  692.         $orderImmatriculation->setStatus(OrderStatusEnum::PAID);
  693.         $this->em->persist($orderImmatriculation);
  694.     }
  695.     private function addDiscountToSubscription(Organization $organizationstring $discount): void
  696.     {
  697.         $subscription SubscriptionUtils::getDomSubscription($organization);
  698.         if (null !== $subscription) {
  699.             $subscription->setDiscountWithoutTax($discount);
  700.             $this->em->persist($subscription);
  701.         }
  702.     }
  703.     private function updateStatusOrganization(Organization $organization): Organization
  704.     {
  705.         if ($organization->getIsNewImmatriculation() && (!$organization->getStatusImmatriculation() || OrganizationStatusImmatriculationEnum::NEW === $organization->getStatusImmatriculation())) {
  706.             $organization->setStatusImmatriculation(OrganizationStatusImmatriculationEnum::NEW_PAYMENT);
  707.         }
  708.         if ($organization->getIsNewTransfert() && (OrganizationStatusTransfertEnum::NEW === $organization->getStatusTransfert() || !$organization->getStatusTransfert())) {
  709.             $organization->setStatusTransfert(OrganizationStatusTransfertEnum::NEW_PAYMENT);
  710.         }
  711.         if ($organization->getIsNewDomiciliation() && OrganizationStatusEnum::NEW === $organization->getStatus()) {
  712.             $organization->setStatus(OrganizationStatusEnum::NEW_PAYMENT);
  713.         }
  714.         $this->em->persist($organization);
  715.         return $organization;
  716.     }
  717.     /**
  718.      * @Route("/regenerate/{id}", name="app_regenerate_invoice", methods={"GET"})
  719.      */
  720.     public function regenerateInvoice(int $id): JsonResponse
  721.     {
  722.         $invoice $this->em->getRepository(Invoice::class)->find($id);
  723.         if (null === $invoice->getOrganization()) {
  724.             return $this->json(['result' => 'Organization not found'], Response::HTTP_NOT_FOUND);
  725.         }
  726.         $this->invoiceUtils->generateInvoice($invoice);
  727.         $this->em->persist($invoice);
  728.         $this->em->flush();
  729.         $path $this->router->generate(
  730.             'app_invoice_view',
  731.             ['id' => $this->encryptor->encrypt($invoice->getId())],
  732.             UrlGeneratorInterface::ABSOLUTE_URL
  733.         );
  734.         return $this->json(['viewInvoicePath' => $path.'/'.$invoice->getOrganization()->getInvoiceToken()], Response::HTTP_OK);
  735.     }
  736.     /** create invoice by organization.
  737.      * @Route("/create/{orgaId}", name="app_invoice_create_by_organization", methods={"POST"})
  738.      */
  739.     public function createInvoiceByOrganization(int $orgaId): JsonResponse
  740.     {
  741.         $organization $this->em->getRepository(Organization::class)->find($orgaId);
  742.         // create invoice
  743.         $invoice = new Invoice();
  744.         $invoice->setOrganization($organization);
  745.         $invoice->setObject('Frais plis d’huissier');
  746.         $invoice->setIsSent(true);
  747.         $invoice->setUser(null);
  748.         $invoice->setIsNewOrganization(false);
  749.         $invoice->setStatus(InvoiceStatusEnum::SENT);
  750.         /** @var Product $product */
  751.         $product $this->em->getRepository(Product::class)->findOneBy([
  752.             'uniqueKey' => ProductKeyEnum::FRAIS_PLIS_HUISSIER,
  753.         ]);
  754.         if ($product) {
  755.             // create item
  756.             $item = new Item();
  757.             $item->setUnitPriceWithoutTax($product->getPriceWithoutTax());
  758.             $item->setQuantity(1);
  759.             $item->setProduct($product);
  760.             $invoice->addItem($item);
  761.         }
  762.         InvoiceUtils::setPrices($invoice);
  763.         // create payment
  764.         $newPayment = new Payment();
  765.         $newPayment->setType(PaymentTypeEnum::GOCARDLESS);
  766.         $newPayment->setAmount($invoice->getPrice());
  767.         $invoice->addPayment($newPayment);
  768.         $this->em->persist($invoice);
  769.         $this->em->flush();
  770.         return new JsonResponse(['status' => Response::HTTP_CREATED]);
  771.     }
  772.     /**
  773.      * @Route("/{id}/billing-report-debug", name="app_invoice_report_debug", methods={"GET"}, requirements={"id"="\d+"})
  774.      */
  775.     public function getInvoiceBillingReportDebug(int $id): JsonResponse
  776.     {
  777.         $this->denyAccessUnlessGranted('ROLE_DEV');
  778.         /**
  779.          * @var Invoice $invoice
  780.          */
  781.         $invoice $this->invoiceRepository->find($id);
  782.         if (null === $invoice) {
  783.             return $this->json(['result' => 'Invoice not found'], Response::HTTP_NOT_FOUND);
  784.         }
  785.         $organization $invoice->getOrganization();
  786.         if (!$organization) {
  787.             return $this->json(['result' => 'Organization not found'], Response::HTTP_NOT_FOUND);
  788.         }
  789.         $data InvoiceMapper::mapForBillingReport($invoice);
  790.         $data['customer'] = OrganizationMapper::mapForBillingReport($organization);
  791.         $data['categories'] = $invoice->getAmountByCategories();
  792.         return new JsonResponse($dataResponse::HTTP_OK);
  793.     }
  794. }