<?php
namespace App\Controller\Api;
use App\CustomInterface\SubscriptionUtilsInterface;
use App\Enum\DiscountCodeTypeEnum;
use App\Enum\InvoiceStatusEnum;
use App\Enum\LegalStatusTypeEnum;
use App\Enum\OrderCategoriesEnum;
use App\Enum\OrderStatusEnum;
use App\Enum\OrganizationStatusEnum;
use App\Enum\OrganizationStatusImmatriculationEnum;
use App\Enum\OrganizationStatusTransfertEnum;
use App\Enum\PaymentRecurrenceEnum;
use App\Enum\PaymentStatusEnum;
use App\Enum\PaymentTypeEnum;
use App\Enum\ProductKeyEnum;
use App\Enum\SubscriptionPackEnum;
use App\Repository\OrderRepository;
use App\Repository\SubscriptionRepository;
use App\Service\EncryptorDataUtils;
use App\Service\InvoiceFilesService;
use App\Service\Invoices\InvoiceService;
use App\Service\SegmentAPI;
use App\Service\SubscriptionUtils;
use App\Traits\SentryNotifyTrait;
use App\Utils\InvoiceUtils;
use App\Utils\OrderUtils;
use Doctrine\ORM\EntityManagerInterface;
use Evo\Infrastructure\Mapper\Invoice\InvoiceMapper;
use Evo\Infrastructure\Mapper\Organization\OrganizationMapper;
use Evo\Infrastructure\MappingORM\DiscountCode;
use Evo\Infrastructure\MappingORM\Invoice;
use Evo\Infrastructure\MappingORM\Item;
use Evo\Infrastructure\MappingORM\Order;
use Evo\Infrastructure\MappingORM\Organization;
use Evo\Infrastructure\MappingORM\Payment;
use Evo\Infrastructure\MappingORM\Product;
use Evo\Infrastructure\MappingORM\Subscription;
use Evo\Infrastructure\Repository\InvoiceRepository;
use Knp\Snappy\Pdf;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Security;
use SymfonyBundles\RedisBundle\Redis\ClientInterface;
use Twig\Environment;
/**
* @Route("/invoice")
*/
class InvoiceController extends AbstractController
{
use SentryNotifyTrait;
public const SERVICE_NAME = '[INVOICE_CONTROLLER] :: ';
private EntityManagerInterface $em;
private SegmentAPI $segmentAPI;
private SubscriptionUtilsInterface $subscriptionUtils;
private EncryptorDataUtils $encryptor;
private InvoiceUtils $invoiceUtils;
private InvoiceRepository $invoiceRepository;
private Environment $twig;
private Pdf $pdf;
private InvoiceFilesService $invoiceFilesService;
private KernelInterface $kernel;
private UrlGeneratorInterface $router;
private OrderUtils $orderUtils;
private ClientInterface $client;
private MessageBusInterface $bus;
private InvoiceService $invoiceService;
public function __construct(
EntityManagerInterface $em,
InvoiceFilesService $invoiceFilesService,
EncryptorDataUtils $encryptor,
InvoiceUtils $invoiceUtils,
OrderUtils $orderUtils,
SubscriptionUtilsInterface $subscriptionUtils,
SegmentAPI $segmentAPI,
InvoiceRepository $invoiceRepository,
Environment $twig,
Pdf $pdf,
KernelInterface $kernel,
UrlGeneratorInterface $router,
ClientInterface $client,
MessageBusInterface $bus,
InvoiceService $invoiceService
) {
$this->em = $em;
$this->encryptor = $encryptor;
$this->segmentAPI = $segmentAPI;
$this->invoiceUtils = $invoiceUtils;
$this->subscriptionUtils = $subscriptionUtils;
$this->orderUtils = $orderUtils;
$this->invoiceRepository = $invoiceRepository;
$this->twig = $twig;
$this->pdf = $pdf;
$this->invoiceFilesService = $invoiceFilesService;
$this->kernel = $kernel;
$this->router = $router;
$this->client = $client;
$this->bus = $bus;
$this->invoiceService = $invoiceService;
}
/**
* @Route("/view/{id}/{token}", name="app_invoice_view", methods={"GET"})
*
* @param null $token
*
* @return JsonResponse
*/
public function viewInvoice($id, $token = null)
{
$repository = $this->em->getRepository(Invoice::class);
$repositoryOrganization = $this->em->getRepository(Organization::class);
/** @var Organization $_organization */
$_organization = $repositoryOrganization->findOneBy(['invoiceToken' => $token]);
$id = $this->encryptor->decrypt($id);
/** @var Invoice $invoice */
$invoice = $repository->find($id);
if (!$_organization || !$invoice) {
return $this->json(['result' => 'Invoice not found'], Response::HTTP_NOT_FOUND);
}
$organization = $invoice->getOrganization();
if (null !== $organization && $organization->getId() === $_organization->getId()) {
$this->invoiceFilesService->viewFile($invoice->getPath());
}
return $this->json(['result' => 'Document not found'], Response::HTTP_NOT_FOUND);
}
/**
* @Route("/product-category/{id}", name="app_invoice_product_category", methods={"GET"})
*/
public function getInvoiceProductCategory(int $id): JsonResponse
{
/** @var Invoice $invoice */
$invoice = $this->invoiceRepository->find($id);
$productCategories = [];
$items = [];
foreach ($invoice->getItems() as $item) {
$product = $item->getProduct();
if (null !== $product && null !== $product->getCategory()) {
$items[] = [
'name' => $product->getName(),
'price' => $product->getPriceWithoutTax(),
'product_id' => $product->getId(),
'quantity' => $item->getQuantity(),
];
$productCategories[] = (string) $product->getCategory()->getType();
}
}
$category = $this->getInvoiceProductCategoryTitle($productCategories);
return new JsonResponse(['category' => $category, 'items' => $items], Response::HTTP_OK);
}
/**
* @param array<string> $ACategories
*/
private function getInvoiceProductCategoryTitle(array $ACategories): string
{
$occurrences = array_count_values($ACategories);
return (string) array_search(max($occurrences), $occurrences);
}
/**
* @Route("/remaining-to-pay/{organizationId}", name="app_invoice_remaining_to_pay", methods={"GET"})
*/
public function getRemainingToPay(int $organizationId): JsonResponse
{
/** @var InvoiceRepository $repository */
$repository = $this->em->getRepository(Invoice::class);
$invoices = $repository->findBy(['organization' => $organizationId]);
$remainingToPay = 0;
foreach ($invoices as $invoice) {
$creditNoteAmount = 0;
$isPaidOrOverPaid = in_array($invoice->getStatus(), [InvoiceStatusEnum::OVER_PAID, InvoiceStatusEnum::PAID]);
$isWaiting = in_array($invoice->getStatus(), [InvoiceStatusEnum::SENT, InvoiceStatusEnum::LATE, InvoiceStatusEnum::PARTIALLY_PAID]) &&
$this->invoiceUtils->isHavingWaitingPayment($invoice);
if (!$isPaidOrOverPaid && !$isWaiting) {
if ($invoice->getCreditNotes()) {
foreach ($invoice->getCreditNotes() as $creditNote) {
$creditNoteAmount += $creditNote->getAmount();
}
}
$remainingToPay = ($remainingToPay + ($invoice->getPrice() - $invoice->getTotalPaid())) - $creditNoteAmount;
}
}
return new JsonResponse(
[
'remainingToPay' => $remainingToPay,
],
Response::HTTP_OK
);
}
/**
* @Route("/pending-payment/{organizationId}", name="app_invoice_pending_payment", methods={"GET"})
*/
public function getInvoiceWithPendingPayment(int $organizationId): JsonResponse
{
/** @var InvoiceRepository $repository */
$repository = $this->em->getRepository(Invoice::class);
$invoices = $repository->findBy(['organization' => $organizationId]);
$pendingAmount = 0;
foreach ($invoices as $invoice) {
$isWaiting = in_array($invoice->getStatus(), [
InvoiceStatusEnum::SENT,
InvoiceStatusEnum::LATE,
InvoiceStatusEnum::PARTIALLY_PAID,
]) &&
$this->invoiceUtils->isHavingWaitingPayment($invoice);
if ($isWaiting) {
$pendingAmount += $this->invoiceUtils->setTotalWaitingPayment($invoice);
}
}
return new JsonResponse(
[
'pendingAmount' => $pendingAmount,
],
Response::HTTP_OK
);
}
/**
* @Route("/download/{id}", name="app_invoice_download", methods={"GET"})
*/
public function downloadAWSFile($id, ?bool $sendLink = false)
{
$repository = $this->em->getRepository(Invoice::class);
$id = $this->encryptor->decrypt($id);
$invoice = $repository->find($id);
if (null !== $invoice) {
$this->invoiceFilesService->downloadInvoice($invoice->getPath(), $sendLink);
}
return $this->json(['result' => 'Invoice not found'], Response::HTTP_NOT_FOUND);
}
/**
* @Route("/paid", name="app_invoice_paid", methods={"POST", "PUT"})
*/
public function invoicePaid(): JsonResponse
{
return $this->json(['message' => 'success']);
}
/**
* @Route("/check-duplicated-invoice", name="app_invoice_check_duplicated", methods={"GET"})
*/
public function checkDuplicatedInvoice(): JsonResponse
{
$duplicatedInvoices = $this->invoiceRepository->getDuplicatedInvoice(null, true);
$isHavingDuplicatedInvoice = false;
if ((is_countable($duplicatedInvoices) ? count($duplicatedInvoices) : 0) > 0) {
$isHavingDuplicatedInvoice = true;
}
return new JsonResponse(['isHavingDuplicatedInvoice' => $isHavingDuplicatedInvoice], Response::HTTP_OK);
}
/**
* @Route("/generate", name="app_generate_invoice", methods={"POST"})
*
* @throws \JsonException
* @throws \Exception
*/
public function invoice(Request $request): JsonResponse
{
$query = json_decode($request->getContent(), true, 512, JSON_THROW_ON_ERROR);
// get query params
$organizationID = $query['organization'];
$typePayment = $query['type_payment'];
$object = $query['object'];
$amount = $query['amount'];
$pack = $query['pack'] ?? null;
$category = $query['category'] ?? null;
$reference = $query['reference'] ?? null;
$promoCode = $query['promo_code'] ?? null;
$globalDiscountWithoutTax = 0;
$discountCode = null;
$organization = $this->getOrganization((int) $organizationID);
/** @var Subscription $subscription */
$subscription = SubscriptionUtils::getDomSubscription($organization);
if (isset($promoCode)) {
$productRepository = $this->em->getRepository(Product::class);
$packProduct = $productRepository->findOneBy(['uniqueKey' => $pack]);
$discountCode = $this->getPromoCode($promoCode, $amount, $packProduct);
}
if ($discountCode && DiscountCodeTypeEnum::INVOICE_DISCOUNT === $discountCode->getType()) {
$globalDiscountWithoutTax = (float) $discountCode->getFirstDiscountConfiguration()->getAmount();
}
// get items for subscription
$items = [];
switch ($category) {
case 'DOMICILIATION':
$items = $this->generateDomItem($pack, $subscription, $discountCode);
break;
case 'IMMATRICULATION':
$items = $this->generateImmatItems($pack, $discountCode);
break;
case 'DOM_IMMAT':
case 'DOM_TRANSFERT':
$items = $this->generateDomImmatItems($pack, $organization, $category, $subscription, $discountCode);
break;
}
// create payment
$payment = $this->createPayment($typePayment, $amount, $reference, $globalDiscountWithoutTax, $discountCode, $category, $items);
// create invoice
$invoice = $this->invoiceUtils->createInvoice($organization, $object, $amount - $globalDiscountWithoutTax, $globalDiscountWithoutTax, $items, $payment, $discountCode);
$this->em->flush();
if ($discountCode && DiscountCodeTypeEnum::SUBSCRIPTION_DISCOUNT === $discountCode->getType()) {
$this->addDiscountToSubscription($organization, (string) $discountCode->getFirstDiscountConfiguration()->getAmount());
}
$organization = $this->updateStatusOrganization($organization);
$this->em->persist($organization);
if ($pack) {
if ('IMMATRICULATION' === $category) {
$this->updateImmatSubscription($pack, $organization, $subscription);
} else {
$this->updateDomNextPayment($organization, $subscription);
}
}
// segment track
$this->sendIdentityToSegmentAPI($organization);
$this->segmentAPI->trackNewOrder($invoice);
// flush all
$this->em->flush();
$data = [
'id' => $organization->getId(),
'status' => $organization->getStatus(),
'invoice' => $invoice->getId(),
];
return new JsonResponse($data, Response::HTTP_CREATED);
}
/**
* @Route("/create-first-invoice", name="app_create_first_invoice", methods={"POST"})
*
* @throws \JsonException
* @throws \Exception
*/
public function createFirstInvoice(Request $request): JsonResponse
{
$query = json_decode($request->getContent(), true, 512, JSON_THROW_ON_ERROR);
$organizationID = (int) $query['organization'];
$queryItems = $query['products'] ?? [];
// get organization and subscription
$organization = $this->getOrganization($organizationID);
$subscription = SubscriptionUtils::getDomSubscription($organization);
// set up items
$items = [];
$productRepository = $this->em->getRepository(Product::class);
foreach ($queryItems as $queryItem) {
$product = $productRepository->findOneBy(['uniqueKey' => $queryItem['uniqueKey']]);
$items[] = $this->setUpItems($queryItem, $product);
}
// create payment
$paymentType = $query['type_payment'];
$paymentAmount = (float) $query['amount'];
$paymentReference = $query['reference'];
$payment = $this->setUpFirstPayment($paymentType, $paymentAmount, $paymentReference);
// create invoice
$invoiceAmount = (float) $query['amount'];
$invoiceObject = $query['object'];
$discountCode = null;
if (null !== $query['promoCodeId']) {
$discountCodeRepository = $this->em->getRepository(DiscountCode::class);
$discountCode = $discountCodeRepository->findOneBy(['id' => $query['promoCodeId']]);
}
$invoiceGlobalDiscountWithoutTax = (float) $query['discount'];
$invoice = $this->setUpFirstInvoice($organization, $invoiceObject, $invoiceAmount, $invoiceGlobalDiscountWithoutTax, $items, $payment, $discountCode);
// flush invoice
$this->em->flush();
if ($discountCode && DiscountCodeTypeEnum::SUBSCRIPTION_DISCOUNT === $discountCode->getType()) {
$this->addDiscountToSubscription($organization, $query['discount']);
}
$organization = $this->updateStatusOrganization($organization);
$this->em->persist($organization);
$pack = $query['pack'];
$category = $query['category'];
/* Update prepaid months by discount code before updateImmatSubscription & updateDomNextPayment */
$this->subscriptionUtils->updatePrepaidMonthsByDiscountCode($subscription, $discountCode);
// update period start & end date
$this->invoiceService->updatePeriodStartAndEnd($invoice, $discountCode);
$subscriptionRepository = $this->em->getRepository(Subscription::class);
if ($subscription && PaymentRecurrenceEnum::ANNUAL === $subscription->getType()) {
$domStart = $organization->getDomiciliationStartDate();
if ($domStart) {
$subscriptionRepository->setNextPayment($subscription->getId(), $domStart->modify('+1 year'));
} else {
$subscriptionRepository->setNextPayment($subscription->getId(), new \DateTime('+1 year'));
}
}
if ($pack) {
if ('IMMATRICULATION' === $category) {
$this->updateImmatSubscription($pack, $organization, $subscription);
} else {
$this->updateDomNextPayment($organization, $subscription);
}
}
// segment track
$this->sendIdentityToSegmentAPI($organization);
$this->segmentAPI->trackNewOrder($invoice);
// flush all
$this->em->flush();
$returnData = [
'id' => $organization->getId(),
'status' => $organization->getStatus(),
'invoice' => $invoice->getId(),
];
return new JsonResponse($returnData, Response::HTTP_CREATED);
}
private function setUpItems(array $queryItem, Product $product): Item
{
$item = new Item();
$item->setProduct($product);
$item->setUnitPriceWithoutTax((float) $queryItem['priceWithoutTax']);
$item->setQuantity((int) $queryItem['quantity']);
$item->setDiscountWithoutTax((float) $queryItem['discount']);
return $item;
}
private function setUpFirstPayment(string $paymentType, float $paymentAmount, string $paymentReference): Payment
{
$payment = new Payment();
$payment->setType($paymentType);
$payment->setAmount($paymentAmount);
$payment->setStatus(PaymentStatusEnum::SUCCESS);
$payment->setReference($paymentReference);
return $payment;
}
private function setUpFirstInvoice(
Organization $organization,
string $object,
float $amount,
float $globalDiscountWithoutTax,
array $items,
Payment $payment,
?DiscountCode $discountCode
): Invoice {
$invoice = new Invoice();
$invoice->setObject($object);
$invoice->setIsSent(true);
$invoice->setStatus(InvoiceStatusEnum::PAID);
$invoice->setPrice($amount);
if ($organization->isAttachedToPrescriber() && $organization->getPrescriber()) {
$invoice->setOrganization($organization->getPrescriber());
} else {
$invoice->setOrganization($organization);
}
foreach ($items as $item) {
$invoice->addItem($item);
}
if (null !== $discountCode) {
$invoice->setDiscountCode($discountCode);
if (DiscountCodeTypeEnum::INVOICE_DISCOUNT === $discountCode->getType()) {
$invoice->setDiscountWithoutTax($globalDiscountWithoutTax);
} else {
$invoice->setDiscountWithoutTax(0);
}
} else {
$invoice->setDiscountWithoutTax($globalDiscountWithoutTax);
}
$invoice->addPayment($payment);
$this->em->persist($invoice);
return $invoice;
}
private function getPromoCode(int $promoCode, float $amount, ?Product $product): ?DiscountCode
{
$repository = $this->em->getRepository(DiscountCode::class);
/** @var ?DiscountCode $promoCode */
$promoCode = $repository->findOneBy(['id' => $promoCode]);
if (null !== $promoCode) {
$discountAmount = $this->invoiceUtils->discountCodeCalculation($amount, $promoCode);
if (DiscountCodeTypeEnum::PRODUCT_DISCOUNT !== $promoCode->getType()) {
$promoCode->getFirstDiscountConfiguration()->setAmount($discountAmount);
} elseif (null !== $product) {
$discountConfiguration = $promoCode->findDiscountProductTarget($product);
if (null !== $discountConfiguration) {
$discountConfiguration->setAmount($discountAmount);
}
}
return $promoCode;
}
return null;
}
/**
* @throws \Exception
*/
private function updateImmatSubscription(string $pack, Organization $organization, ?Subscription $subscription): void
{
$this->updateStatusOrderImmat();
if (in_array($pack, [SubscriptionPackEnum::CONFORT_MICRO, SubscriptionPackEnum::CONFORT]) &&
LegalStatusTypeEnum::AUTO_ENTREPRENEUR === $organization->getLegalStatus()
) {
$domDate = $organization->getDomiciliationStartDate();
if ($domDate && $subscription) {
$domicilingStartDate = $domDate->getTimestamp();
$frequency = SubscriptionUtils::getFrequency($subscription);
$nextPaymentDate = date('Y-m-d', (int) strtotime($frequency, $domicilingStartDate));
$subscription->setNextPayment(new \DateTime($nextPaymentDate));
$this->em->persist($subscription);
$this->em->flush();
}
}
}
private function updateDomNextPayment(Organization $organization, ?Subscription $subscription): void
{
/** @var SubscriptionRepository $subscriptionRepository */
$subscriptionRepository = $this->em->getRepository(Subscription::class);
$nextPaymentDate = null;
$domDate = $organization->getDomiciliationStartDate();
if ($domDate && $subscription) {
$domicilingStartDate = $domDate->getTimestamp();
$frequency = SubscriptionUtils::getFrequency($subscription);
$nextPaymentDate = date('Y-m-d', (int) strtotime($frequency, $domicilingStartDate));
$subscriptionID = $subscription->getId();
$subscriptionRepository->setNextPayment($subscriptionID, $nextPaymentDate);
}
}
private function createPayment(string $typePayment, float $amount, string $reference, float $globalDiscount, ?DiscountCode $discountCode, string $category, array $items): Payment
{
$finalAmount = $amount;
if ($discountCode &&
in_array($discountCode->getType(), [
DiscountCodeTypeEnum::SUBSCRIPTION_DISCOUNT,
DiscountCodeTypeEnum::PRODUCT_DISCOUNT,
], true)
) {
$countProductWIthDiscount = 1;
$itemsWithDiscount = [];
if (DiscountCodeTypeEnum::PRODUCT_DISCOUNT === $discountCode->getType() &&
in_array($category, ['IMMATRICULATION', 'DOM_IMMAT', 'DOM_TRANSFERT'])
) {
/** @var Item $item */
foreach ($items as $item) {
if ($item->getDiscountWithoutTax() > 0) {
$itemsWithDiscount[] = $item;
}
}
$countProductWIthDiscount = count($itemsWithDiscount);
}
$withDiscountPrice = $finalAmount - ($discountCode->getFirstDiscountConfiguration()->getAmount() * $countProductWIthDiscount);
$finalAmount = ($withDiscountPrice + ($withDiscountPrice * 0.2));
}
if (!$discountCode && $globalDiscount <= 0) {
$finalAmount += $finalAmount * 0.2;
}
if ($globalDiscount > 0) {
$finalAmount = ($finalAmount - $globalDiscount) + (($finalAmount - $globalDiscount) * 0.2);
}
$payment = new Payment();
$payment->setType($typePayment);
$payment->setAmount($finalAmount);
$payment->setStatus(PaymentStatusEnum::SUCCESS);
$payment->setReference($reference);
return $payment;
}
/**
* @return array<Item>
*/
private function generateDomItem(string $pack, Subscription $subscription, ?DiscountCode $discountCode): array
{
$productRepository = $this->em->getRepository(Product::class);
// pack product
/** @var Product $packProduct */
$packProduct = $productRepository->findOneBy(['uniqueKey' => $pack]);
$items = [];
// change if needed to change quantity
$packItem = $this->hydrateItem($packProduct, (float) $subscription->getPriceWithoutTax(), 1, $discountCode);
$this->em->persist($packItem);
// Security deposit product
$depositSecurityProduct = $productRepository->findOneBy(['uniqueKey' => ProductKeyEnum::SECURITY_DEPOSIT]);
$depositItem = $this->hydrateItem($depositSecurityProduct, 0, 1, null);
$this->em->persist($depositItem);
$items[] = $packItem;
$items[] = $depositItem;
return $items;
}
/**
* @return array<Item>
*/
private function generateImmatItems(string $pack, ?DiscountCode $discountCode): array
{
$items = [];
$packProducts = SubscriptionPackEnum::IMMAT_PACK[$pack];
$discountProductKeys = [];
if ($discountCode &&
DiscountCodeTypeEnum::PRODUCT_DISCOUNT === $discountCode->getType()
) {
$discountConfigurations = $discountCode->getDiscountConfigurations();
foreach ($discountConfigurations as $discountConfiguration) {
$targetProducts = $discountConfiguration->getTargetProducts();
foreach ($targetProducts as $targetProduct) {
$discountProductKeys[] = $targetProduct->getUniqueKey();
}
}
}
foreach ($packProducts as $packProduct) {
$isNeedDiscount = in_array($packProduct, $discountProductKeys, true);
$product = $this->em->getRepository(Product::class)->findOneBy(['uniqueKey' => $packProduct]);
if (null === $product) {
throw new \RuntimeException('Product not found');
}
$item = $this->hydrateItem($product, (float) $product->getPriceWithoutTax(), 1, $isNeedDiscount ? $discountCode : null);
$items[] = $item;
}
return $items;
}
/**
* @return array<Item>
*/
private function generateDomImmatItems(
string $pack,
Organization $organization,
string $category,
Subscription $subscription,
?DiscountCode $discountCode
): array {
$orders = $organization->getOrders()->getValues();
$immatItems = $this->orderUtils->getItemsByOrderCategory($orders, $category);
$discountProductKeys = [];
if ($discountCode &&
DiscountCodeTypeEnum::PRODUCT_DISCOUNT === $discountCode->getType()
) {
$discountConfigurations = $discountCode->getDiscountConfigurations();
foreach ($discountConfigurations as $discountConfiguration) {
$targetProducts = $discountConfiguration->getTargetProducts();
foreach ($targetProducts as $targetProduct) {
$discountProductKeys[] = $targetProduct->getUniqueKey();
}
}
}
/** @var Item $item */
foreach ($immatItems as $item) {
$uniqueKey = $item->getProduct()->getUniqueKey();
if ($uniqueKey &&
in_array($uniqueKey, $discountProductKeys, true)) {
if ($discountCode && DiscountCodeTypeEnum::SUBSCRIPTION_DISCOUNT === $discountCode->getType()) {
$item->setDiscountWithoutTax($discountCode->getFirstDiscountConfiguration()->getAmount());
}
if ($discountCode && DiscountCodeTypeEnum::PRODUCT_DISCOUNT === $discountCode->getType()) {
$discountConfiguration = $discountCode->findDiscountProductTarget($item->getProduct());
if ($discountConfiguration) {
$item->setDiscountWithoutTax($discountConfiguration->getAmount());
}
}
}
}
$domItems = $this->generateDomItem($pack, $subscription, $discountCode);
return array_merge_recursive($immatItems, $domItems);
}
/**
* @throws \Exception
*/
private function getOrganization(int $organizationID): Organization
{
$repositoryOrganization = $this->em->getRepository(Organization::class);
/** @var ?Organization $organization */
$organization = $repositoryOrganization->find($organizationID);
if (is_null($organization)) {
throw new \Exception('Organization not found');
}
return $organization;
}
private function hydrateItem(Product $product, float $priceWithoutTax, int $quantity, ?DiscountCode $discountCode): Item
{
$item = new Item();
$item->setProduct($product);
$item->setUnitPriceWithoutTax($priceWithoutTax);
$item->setQuantity($quantity);
if ($discountCode && DiscountCodeTypeEnum::SUBSCRIPTION_DISCOUNT === $discountCode->getType()) {
$item->setDiscountWithoutTax($discountCode->getFirstDiscountConfiguration()->getAmount());
}
if ($discountCode && DiscountCodeTypeEnum::PRODUCT_DISCOUNT === $discountCode->getType()) {
$discountForProduct = $discountCode->findDiscountProductTarget($product);
if ($discountForProduct) {
$item->setDiscountWithoutTax($discountForProduct->getAmount());
}
}
return $item;
}
private function sendIdentityToSegmentAPI(Organization $organization): void
{
if ([] !== $organization->getUsers()) {
$firstUser = $organization->getUsers()[0];
$this->segmentAPI->identify([
'userId' => $organization->getUniqId(),
'traits' => [
'id_user' => $firstUser->getUniqId(),
'id_organization' => $organization->getUniqId(),
'token' => $organization->getInvoiceToken(),
],
]);
}
}
private function updateStatusOrderImmat(): void
{
/** @var OrderRepository $orderRepository */
$orderRepository = $this->em->getRepository(Order::class);
/** @var Order $orderImmatriculation */
$orderImmatriculation = $orderRepository->findOneBy(['category' => OrderCategoriesEnum::IMMATRICULATION]);
$orderImmatriculation->setStatus(OrderStatusEnum::PAID);
$this->em->persist($orderImmatriculation);
}
private function addDiscountToSubscription(Organization $organization, string $discount): void
{
$subscription = SubscriptionUtils::getDomSubscription($organization);
if (null !== $subscription) {
$subscription->setDiscountWithoutTax($discount);
$this->em->persist($subscription);
}
}
private function updateStatusOrganization(Organization $organization): Organization
{
if ($organization->getIsNewImmatriculation() && (!$organization->getStatusImmatriculation() || OrganizationStatusImmatriculationEnum::NEW === $organization->getStatusImmatriculation())) {
$organization->setStatusImmatriculation(OrganizationStatusImmatriculationEnum::NEW_PAYMENT);
}
if ($organization->getIsNewTransfert() && (OrganizationStatusTransfertEnum::NEW === $organization->getStatusTransfert() || !$organization->getStatusTransfert())) {
$organization->setStatusTransfert(OrganizationStatusTransfertEnum::NEW_PAYMENT);
}
if ($organization->getIsNewDomiciliation() && OrganizationStatusEnum::NEW === $organization->getStatus()) {
$organization->setStatus(OrganizationStatusEnum::NEW_PAYMENT);
}
$this->em->persist($organization);
return $organization;
}
/**
* @Route("/regenerate/{id}", name="app_regenerate_invoice", methods={"GET"})
*/
public function regenerateInvoice(int $id): JsonResponse
{
$invoice = $this->em->getRepository(Invoice::class)->find($id);
if (null === $invoice->getOrganization()) {
return $this->json(['result' => 'Organization not found'], Response::HTTP_NOT_FOUND);
}
$this->invoiceUtils->generateInvoice($invoice);
$this->em->persist($invoice);
$this->em->flush();
$path = $this->router->generate(
'app_invoice_view',
['id' => $this->encryptor->encrypt($invoice->getId())],
UrlGeneratorInterface::ABSOLUTE_URL
);
return $this->json(['viewInvoicePath' => $path.'/'.$invoice->getOrganization()->getInvoiceToken()], Response::HTTP_OK);
}
/** create invoice by organization.
* @Route("/create/{orgaId}", name="app_invoice_create_by_organization", methods={"POST"})
*/
public function createInvoiceByOrganization(int $orgaId): JsonResponse
{
$organization = $this->em->getRepository(Organization::class)->find($orgaId);
// create invoice
$invoice = new Invoice();
$invoice->setOrganization($organization);
$invoice->setObject('Frais plis d’huissier');
$invoice->setIsSent(true);
$invoice->setUser(null);
$invoice->setIsNewOrganization(false);
$invoice->setStatus(InvoiceStatusEnum::SENT);
/** @var Product $product */
$product = $this->em->getRepository(Product::class)->findOneBy([
'uniqueKey' => ProductKeyEnum::FRAIS_PLIS_HUISSIER,
]);
if ($product) {
// create item
$item = new Item();
$item->setUnitPriceWithoutTax($product->getPriceWithoutTax());
$item->setQuantity(1);
$item->setProduct($product);
$invoice->addItem($item);
}
InvoiceUtils::setPrices($invoice);
// create payment
$newPayment = new Payment();
$newPayment->setType(PaymentTypeEnum::GOCARDLESS);
$newPayment->setAmount($invoice->getPrice());
$invoice->addPayment($newPayment);
$this->em->persist($invoice);
$this->em->flush();
return new JsonResponse(['status' => Response::HTTP_CREATED]);
}
/**
* @Route("/{id}/billing-report-debug", name="app_invoice_report_debug", methods={"GET"}, requirements={"id"="\d+"})
*/
public function getInvoiceBillingReportDebug(int $id): JsonResponse
{
$this->denyAccessUnlessGranted('ROLE_DEV');
/**
* @var Invoice $invoice
*/
$invoice = $this->invoiceRepository->find($id);
if (null === $invoice) {
return $this->json(['result' => 'Invoice not found'], Response::HTTP_NOT_FOUND);
}
$organization = $invoice->getOrganization();
if (!$organization) {
return $this->json(['result' => 'Organization not found'], Response::HTTP_NOT_FOUND);
}
$data = InvoiceMapper::mapForBillingReport($invoice);
$data['customer'] = OrganizationMapper::mapForBillingReport($organization);
$data['categories'] = $invoice->getAmountByCategories();
return new JsonResponse($data, Response::HTTP_OK);
}
}