src/Controller/Admin/Sales/SalesController.php line 568

Open in your IDE?
  1. <?php
  2. namespace App\Controller\Admin\Sales;
  3. use App\Dto\CalculatorProductsCostsDTO;
  4. use App\Dto\NewStockDto\SalesProductsDto;
  5. use App\Dto\PaginationDTO;
  6. use App\Dto\SalesProductsDtoBuilder;
  7. use App\Entity\Customer;
  8. use App\Entity\MeasurementUnits;
  9. use App\Entity\Payment;
  10. use App\Entity\ProductsSold;
  11. use App\Entity\Sales;
  12. use App\Entity\SalesHistory;
  13. use App\Entity\Stock;
  14. use App\Entity\StockTransaction;
  15. use App\Entity\User;
  16. use App\Entity\Warehouse;
  17. use App\Enum\SalesHistoryEventType;
  18. use App\Enum\SalesStatus;
  19. use App\Enum\SalesType;
  20. use App\Exception\IncorrectAmountException;
  21. use App\Exception\StockNotAvaibleException;
  22. use App\Exception\StockNotFoundException;
  23. use App\Form\CustomerType;
  24. use App\Form\PaymentType;
  25. use App\Form\ProductsSoldType;
  26. use App\Form\SalesFormType;
  27. use App\Repository\SalesRepository;
  28. use App\Repository\StockRepository;
  29. use App\Services\Customer\CustomerService;
  30. use App\Services\MailService;
  31. use App\Services\MeasurementConversions\MeasurementConversionsService;
  32. use App\Services\PDFService;
  33. use App\Services\LogService;
  34. use App\Services\Payment\PaymentMethodService;
  35. use App\Services\Payment\PaymentReminderService;
  36. use App\Services\Payment\PaymentService;
  37. use App\Services\Product\ProductService;
  38. use App\Services\Sales\Impl\SalesProductsServiceImpl;
  39. use App\Services\Sales\Impl\SalesServiceImpl;
  40. use App\Services\Sales\InvoiceService;
  41. use App\Services\Sales\SalesHistoryService;
  42. use App\Services\Sales\SalesService;
  43. use App\Repository\SalesReturnRepository;
  44. use App\Services\Stock\StockInfoService;
  45. use App\Services\Stock\StockService;
  46. use App\Services\Stock\StockTransferService;
  47. use App\Services\StockTransactionService;
  48. use App\Services\Warehouse\WarehouseService;
  49. use App\Utils\CurrencyHelper;
  50. use App\Utils\MailTemplateHelper;
  51. use Doctrine\DBAL\LockMode;
  52. use Doctrine\Common\Collections\ArrayCollection;
  53. use Doctrine\ORM\EntityManagerInterface;
  54. use Doctrine\ORM\NonUniqueResultException;
  55. use Doctrine\ORM\NoResultException;
  56. use Exception;
  57. use Knp\Bundle\SnappyBundle\Snappy\Response\PdfResponse;
  58. use Knp\Snappy\Pdf;
  59. use PhpOffice\PhpSpreadsheet\IOFactory;
  60. use Psr\Log\LoggerInterface;
  61. use Symfony\Bridge\Twig\Mime\TemplatedEmail;
  62. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  63. use Symfony\Component\HttpFoundation\BinaryFileResponse;
  64. use Symfony\Component\HttpFoundation\JsonResponse;
  65. use Symfony\Component\HttpFoundation\Request;
  66. use Symfony\Component\HttpFoundation\Response;
  67. use Symfony\Component\HttpFoundation\ResponseHeaderBag;
  68. use Symfony\Component\HttpKernel\KernelInterface;
  69. use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
  70. use Symfony\Component\Mailer\Mailer;
  71. use Symfony\Component\Mailer\MailerInterface;
  72. use Symfony\Component\Mailer\Transport;
  73. use Symfony\Component\Mime\Address;
  74. use Symfony\Component\Mime\Email;
  75. use Symfony\Component\Routing\Annotation\Route;
  76. use Symfony\Component\Security\Core\User\UserInterface;
  77. use Symfony\Component\Serializer\SerializerInterface;
  78. use Symfony\Contracts\Translation\TranslatorInterface;
  79. class SalesController extends AbstractController
  80. {
  81.     private const STOCK_EXEMPT_PRODUCT_TYPES = ['INSTALLATION_SERVICE''MAINTENANCE_SERVICE''CONSULTATION_SERVICE''TREE'];
  82.     private const NEGATIVE_PRICE_ALLOWED_PRODUCT_TYPES = [];
  83.     private const MONEY_SCALE 2;
  84.     private StockService $stockService;
  85.     private SerializerInterface $serializer;
  86.     private WarehouseService $warehouseService;
  87.     private SalesService $salesService;
  88.     private $projectDir;
  89.     private StockTransferService $stockTransferService;
  90.     private ProductService $productService;
  91.     private StockInfoService $stockInfoService;
  92.     private EntityManagerInterface $entityManager;
  93.     private StockRepository $stockRepository;
  94.     private CustomerService $customerService;
  95.     private PaymentMethodService $paymentMethodService;
  96.     private PaymentService $paymentService;
  97.     private PaymentReminderService $paymentReminderService;
  98.     private LogService $logService;
  99.     private StockTransactionService $stockTransactionService;
  100.     private LoggerInterface $logger;
  101.     private SalesProductsServiceImpl $salesProductsServiceImpl;
  102.     private SalesHistoryService $salesHistoryService;
  103.     private SalesReturnRepository $salesReturnRepository;
  104.     private TranslatorInterface $translator;
  105.     private SalesServiceImpl $salesServiceImpl;
  106.     private Pdf $knpSnappyPdf;
  107.     private InvoiceService $invoiceService;
  108.     private PDFService $pdfService;
  109.     private MeasurementConversionsService $measurementConversionsService;
  110.     /**
  111.      * @param KernelInterface $kernel
  112.      * @param StockService $stockService
  113.      * @param SerializerInterface $serializer
  114.      * @param WarehouseService $warehouseService
  115.      * @param SalesService $salesService
  116.      * @param StockTransferService $stockTransferService
  117.      * @param ProductService $productService
  118.      * @param StockInfoService $stockInfoService
  119.      * @param EntityManagerInterface $entityManager
  120.      * @param StockRepository $stockRepository
  121.      * @param CustomerService $customerService
  122.      * @param PaymentMethodService $paymentMethodService
  123.      * @param PaymentService $paymentService
  124.      * @param PaymentReminderService $paymentReminderService
  125.      * @param LogService $logService
  126.      * @param StockTransactionService $stockTransactionService
  127.      * @param LoggerInterface $logger
  128.      * @param SalesProductsServiceImpl $salesProductsServiceImpl
  129.      * @param TranslatorInterface $translator
  130.      * @param SalesServiceImpl $salesServiceImpl
  131.      * @param Pdf $knpSnappyPdf
  132.      * @param InvoiceService $invoiceService
  133.      * @param PDFService $pdfService
  134.      * @param MeasurementConversionsService $measurementConversionsService
  135.      */
  136.     public function __construct(
  137.         KernelInterface $kernel,
  138.         StockService $stockService,
  139.         SerializerInterface $serializer,
  140.         WarehouseService $warehouseService,
  141.         SalesService $salesService,
  142.         StockTransferService $stockTransferService,
  143.         ProductService $productService,
  144.         StockInfoService $stockInfoService,
  145.         EntityManagerInterface $entityManager,
  146.         StockRepository $stockRepository,
  147.         CustomerService $customerService,
  148.         PaymentMethodService $paymentMethodService,
  149.         PaymentService $paymentService,
  150.         PaymentReminderService $paymentReminderService,
  151.         LogService $logService,
  152.         StockTransactionService $stockTransactionService,
  153.         LoggerInterface $logger,
  154.         SalesProductsServiceImpl $salesProductsServiceImpl,
  155.         SalesHistoryService $salesHistoryService,
  156.         SalesReturnRepository $salesReturnRepository,
  157.         TranslatorInterface $translator,
  158.         SalesServiceImpl $salesServiceImpl,
  159.         Pdf $knpSnappyPdf,
  160.         InvoiceService $invoiceService,
  161.         PDFService $pdfService,
  162.         MeasurementConversionsService $measurementConversionsService
  163.     ) {
  164.         $this->stockService $stockService;
  165.         $this->serializer $serializer;
  166.         $this->warehouseService $warehouseService;
  167.         $this->salesService $salesService;
  168.         $this->stockTransferService $stockTransferService;
  169.         $this->productService $productService;
  170.         $this->stockInfoService $stockInfoService;
  171.         $this->entityManager $entityManager;
  172.         $this->stockRepository $stockRepository;
  173.         $this->customerService $customerService;
  174.         $this->paymentMethodService $paymentMethodService;
  175.         $this->paymentService $paymentService;
  176.         $this->paymentReminderService $paymentReminderService;
  177.         $this->logService $logService;
  178.         $this->stockTransactionService $stockTransactionService;
  179.         $this->logger $logger;
  180.         $this->salesProductsServiceImpl $salesProductsServiceImpl;
  181.         $this->salesHistoryService $salesHistoryService;
  182.         $this->salesReturnRepository $salesReturnRepository;
  183.         $this->translator $translator;
  184.         $this->salesServiceImpl $salesServiceImpl;
  185.         $this->projectDir $kernel->getProjectDir();
  186.         $this->knpSnappyPdf $knpSnappyPdf;
  187.         $this->invoiceService $invoiceService;
  188.         $this->pdfService $pdfService;
  189.         $this->measurementConversionsService $measurementConversionsService;
  190.     }
  191.     /**
  192.      * @throws StockNotAvaibleException
  193.      * @throws StockNotFoundException
  194.      */
  195.     #[Route('/sales'name'app_admin_sales')]
  196.     public function index(Request $requestSalesServiceImpl $salesServiceImplTranslatorInterface $translator): Response
  197.     {
  198.         $paymentMethods $this->paymentMethodService->getPaymentMethods();
  199.         $sales = new Sales();
  200.         $sales->setInvoiceNumber($translator->trans('willBeCreatedAutomatically'));
  201.         $form $this->createForm(SalesFormType::class, $sales);
  202.         $form->handleRequest($request);
  203.         $soldProducts = new ProductsSold();
  204.         $soldProductsFrom $this->createForm(ProductsSoldType::class, $soldProducts);
  205.         $customer = new Customer();
  206.         $customer->setCode($this->customerService->createCustomerCode());
  207.         $customerForm $this->createForm(CustomerType::class, $customer);
  208.         $customerForm->handleRequest($request);
  209.         $payment = new Payment();
  210.         $paymentForm $this->createForm(PaymentType::class, $payment);
  211.         $paymentForm->handleRequest($request);
  212.         $stocks $this->stockService->getAll();
  213.         $warehouses $this->warehouseService->getAll();
  214.         if ($customerForm->isSubmitted() && $customerForm->isValid()) {
  215.             $customer $customerForm->getData();
  216.             $customers $this->customerService->findByFullName($customer->getFullName());
  217.             $this->addFlash('success''Müşteri başarıyla eklendi. Listeden seçebilirsiniz.');
  218.             $email $customer->getEmail();
  219.             $customer->setEmail(mb_strtolower($email'UTF-8'));
  220.             $this->customerService->save($customerForm->getData());
  221.         }
  222.         return $this->render('admin/sales/index.html.twig', [
  223.             'controller_name' => 'SalesController',
  224.             'form' => $form->createView(),
  225.             'productsSoldForm' => $soldProductsFrom->createView(),
  226.             'customerForm' => $customerForm->createView(),
  227.             'paymentForm' => $paymentForm->createView(),
  228.             'warehouses' => $warehouses,
  229.             'paymentMethods' => $paymentMethods,
  230.         ]);
  231.     }
  232.     // Duplicate proforma
  233.     #[Route('/sales/duplicate/{id}'name'app_admin_sales_duplicate')]
  234.     public function duplicateProforma(Request $requestSalesServiceImpl $salesServiceImplTranslatorInterface $translator)
  235.     {
  236.         $paymentMethods $this->paymentMethodService->getPaymentMethods();
  237.         $sales $this->salesService->getEntityById($request->get('id'));
  238.         //        dd($sales->getProductsSolds()->get(0));
  239.         $sales->setInvoiceNumber($translator->trans('willBeCreatedAutomatically'));
  240.         $form $this->createForm(SalesFormType::class, $sales);
  241.         $form->handleRequest($request);
  242.         $soldProducts = new ProductsSold();
  243.         $soldProductsFrom $this->createForm(ProductsSoldType::class, $soldProducts);
  244.         $customer = new Customer();
  245.         $customer->setCode($this->customerService->createCustomerCode());
  246.         $customerForm $this->createForm(CustomerType::class, $customer);
  247.         $customerForm->handleRequest($request);
  248.         $payment = new Payment();
  249.         $paymentForm $this->createForm(PaymentType::class, $payment);
  250.         $paymentForm->handleRequest($request);
  251.         $stocks $this->stockService->getAll();
  252.         $warehouses $this->warehouseService->getAll();
  253.         if ($customerForm->isSubmitted() && $customerForm->isValid()) {
  254.             $customer $customerForm->getData();
  255.             $customers $this->customerService->findByFullName($customer->getFullName());
  256.             if (count($customers) > 0) {
  257.                 $this->addFlash('error''Bu müşteri kaydı zaten var ! Listeden seçip devam edebilirsiniz.');
  258.             } else {
  259.                 $this->addFlash('success''Müşteri başarıyla eklendi. Listeden seçebilirsiniz.');
  260.                 $email $customer->getEmail();
  261.                 $customer->setEmail(mb_strtolower($email'UTF-8'));
  262.                 $this->customerService->save($customerForm->getData());
  263.             }
  264.         }
  265.         return $this->render('admin/sales/index.html.twig', [
  266.             'sales' => $sales,
  267.             'controller_name' => 'SalesController',
  268.             'form' => $form->createView(),
  269.             'productsSoldForm' => $soldProductsFrom->createView(),
  270.             'customerForm' => $customerForm->createView(),
  271.             'paymentForm' => $paymentForm->createView(),
  272.             'warehouses' => $warehouses,
  273.             'paymentMethods' => $paymentMethods,
  274.             'sales'
  275.         ]);
  276.     }
  277.     #[Route('/sales/create'name'app_admin_sales_create')]
  278.     public function create(Request $requestSalesServiceImpl $salesServiceImpl): Response
  279.     {
  280.         $this->logger->info("sales", [
  281.             'title' => 'Satış işlemi başlatıldı',
  282.             'request' => $request->request->all(),
  283.         ]);
  284.         $this->entityManager->beginTransaction();
  285.         $products $request->get('products');
  286.         $payments $request->get('payments');
  287.         $soldList $request->get('soldList');
  288.         $invoice $request->get('invoice');
  289.         $warehouseId $soldList[0]['warehouse'];
  290.         $salesType $invoice['salesType'] ?? 'proforma';
  291.         $intendedStatus $this->resolveSalesStatus($invoice['salesStatus'] ?? null);
  292.         try {
  293.             // Stok kontrolü: yalnızca COMPLETED veya PENDING_ITEMS durumunda yapılır
  294.             if ($this->shouldAffectStock($intendedStatus)) {
  295.                 $this->checkStocks($soldList);
  296.             }
  297.             // Ürün bazlı indirim kdv gibi işlemler yapılıp total fiyatların frontend ile aynı olup olmadığı kontrol ediliyor.
  298.             foreach ($soldList as $soldProduct) {
  299.                 $this->logger->info("sales", [
  300.                     'title' => 'Ürün bazlı indirim kdv hesaplanıyor...',
  301.                     'product' => $soldProduct,
  302.                 ]);
  303.                 $this->assertUnitPriceAllowed($soldProduct);
  304.                 $this->calculateTotalPrice($soldProduct);
  305.             }
  306.             // Ödeme planı ile ürünlerin toplam fiyatlarının eşitliği kontrol ediliyor.
  307.             $totalPrice $this->checkPaymentAndAmountIsEqual($payments$soldList);
  308.             $repo $this->entityManager->getRepository(Sales::class);
  309.             $invoiceNumber $salesServiceImpl->createProformaNumber();
  310.             $sale $repo->findBy(['invoiceNumber' => $invoiceNumber]);
  311.             //            dd($request);
  312.             if ($sale != null) {
  313.                 throw new Exception(sprintf("%s fatura numarası ile kayıtlı bir fatura var. Lütfen kontrol ediniz."$invoice['invoiceNumber']));
  314.             } else {
  315.                 $soldProductsArray = new ArrayCollection();
  316.                 for ($i 0$i count($soldList); $i++) {
  317.                     $product $this->productService->getEntityId($soldList[$i]['productid']);
  318.                     $calculatorDto $this->stockRepository->calculateCost($soldList[$i]['productid'], $warehouseId);
  319.                     $builder = new SalesProductsDtoBuilder();
  320.                     $soldProductsArray->add(
  321.                         $builder
  322.                             ->setProductName($soldList[$i]['productName'])
  323.                             ->setMeasurement($product->getMeasurement()?->getMeasurement())
  324.                             ->setQuantity($soldList[$i]['quantity'])
  325.                             ->setUnitPriceFob($calculatorDto->getPriceFob())
  326.                             ->setUnitPriceNavlun($calculatorDto->getPriceNavlun())
  327.                             ->setTotalUnitPrice($calculatorDto->getAverageCostPrice())
  328.                             ->setTotalUnitPurchasePrice($soldList[$i]['totalPurchasePrice'])
  329.                             ->setProductId($product->getId())
  330.                             ->setTotalUnitPrice($soldList[$i]['unitPrice'])
  331.                             ->setWarehouseId($warehouseId)
  332.                             ->setTax($soldList[$i]['tax'])
  333.                             ->setTotalSalePrice($soldList[$i]['totalPurchasePrice'])
  334.                             ->setDiscount($soldList[$i]['discount'])
  335.                             ->setUnAllocatedQuantity($soldList[$i]['unAllocatedQuantity'])
  336.                             ->setSelectedUnitId($soldList[$i]['selectedUnitId'])
  337.                             ->build()
  338.                     );
  339.                 }
  340.                 $customer $this->customerService->getCustomerById($invoice['customer']);
  341.                 $sales = new Sales();
  342.                 $sales->setSalesDate(new \DateTime($invoice['salesDate']));
  343.                 $sales->setCustomer($customer);
  344.                 $sales->setStatus($intendedStatus);
  345.                 $userRepo $this->entityManager->getRepository(User::class);
  346.                 $sales->setSeller($userRepo->find($invoice['seller']));
  347.                 $sales->setInvoiceNumber($invoice['invoiceNumber']);
  348.                 $sales->setTotalPurchasePrice($totalPrice);
  349.                 $sales->setDescription($invoice['description']);
  350.                 $sales->setShippingNotes($invoice['shippingNotes']);
  351.                 $sales->setPaymentNotes($invoice['paymentNotes']);
  352.                 $performedBy $this->getUser();
  353.                 $sales $this->salesService->create(
  354.                     $sales,
  355.                     $soldProductsArray,
  356.                     $salesType,
  357.                     $performedBy instanceof User $performedBy null
  358.                 );
  359.                 $this->createPayments($payments$customer$sales);
  360.                 $this->entityManager->commit();
  361.                 return new JsonResponse(['success' => true'id' => $sales->getId()]);
  362.             }
  363.         } catch (Exception $e) {
  364.             $this->entityManager->rollback();
  365.             $message $e->getMessage();
  366.             return new JsonResponse(['status' => 'error''message' => $message], 400);
  367.         }
  368.         return new JsonResponse(['status' => 'success']);
  369.     }
  370.     #[Route('/sales/un-allocated-products'name'un_allocated_products_index')]
  371.     public function getUnAllocatedProducts(Request $request)
  372.     {
  373.         $warehouses $this->warehouseService->getAll();
  374.         if ($request->getMethod() == 'POST') {
  375.             if (isset($request->get('search')['value']))
  376.                 $s $request->get('search')['value'];
  377.             else
  378.                 $s '';
  379.             if (isset($request->get('order')[0]['column']))
  380.                 $column $request->get('order')[0]['column'];
  381.             else
  382.                 $column 0;
  383.             if (isset($request->get('order')[0]['dir']))
  384.                 $dir $request->get('order')[0]['dir'];
  385.             else
  386.                 $dir 'ASC';
  387.             $startDate $request->get('startDate');
  388.             $finishDate $request->get('finishDate');
  389.             $limit $request->get('length');
  390.             if ($request->get('start'))
  391.                 $page + ($request->get('start') / $limit);
  392.             else
  393.                 $page 1;
  394.             $warehouseid $request->get('warehouseid');
  395.             $result $this->salesProductsServiceImpl->getAllUnAllocatedProductsPaginate(
  396.                 $s,
  397.                 $column,
  398.                 $dir,
  399.                 $page,
  400.                 $limit,
  401.                 $startDate,
  402.                 $finishDate,
  403.                 $warehouseid
  404.             );
  405.             return $this->json($result);
  406.         }
  407.         return $this->render('admin/sales/unallocated_products.html.twig', [
  408.             'warehouses' => $warehouses
  409.         ]);
  410.     }
  411.     /**
  412.      * @throws NonUniqueResultException
  413.      * @throws NoResultException
  414.      */
  415.     #[Route('/sales/un-allocated-products/allocate'name'un_allocated_products_allocate')]
  416.     public function allocateProduct(Request $request)
  417.     {
  418.         $content $request->getContent();
  419.         $data json_decode($contenttrue);
  420.         $id $data["id"];
  421.         $allocatedQuantity $data["allocatedQuantity"];
  422.         $customer $data["customer"];
  423.         $measurement $data["measurement"];
  424.         $measurementUnit $data["measurementUnit"];
  425.         $productCode $data["productCode"];
  426.         $productName $data["productName"];
  427.         $salesDate $data["salesDate"];
  428.         $sellerName $data["sellerName"];
  429.         $stockInfo $data["stockInfo"];
  430.         $totalSold $data["totalSold"];
  431.         $unAllocatedQuantity $data["unAllocatedQuantity"];
  432.         $warehouseName $data["warehouseName"];
  433.         $productSold $this->salesProductsServiceImpl->getEntityById($id);
  434.         $quantityToAllocate $allocatedQuantity $productSold->getQuantity();
  435.         $stock $this->stockInfoService->getStockInfoByProductAndWarehouse(
  436.             $productSold->getProduct(),
  437.             $productSold->getSales()->getWarehouse()
  438.         );
  439.         if ($quantityToAllocate $stock->getTotalQuantity()) {
  440.             return $this->json([
  441.                 'message' => sprintf('Stok miktarı yetersiz, depodaki stok miktarı = %s'$stock->getTotalQuantity())
  442.             ], 400);
  443.         }
  444.         //        dd($quantityToAllocate, CurrencyHelper::convertToFloat($productSold->getUnAllocatedQuantity()));
  445.         $unAllocatedQuantityFromProduct round(CurrencyHelper::convertToFloat($productSold->getUnAllocatedQuantity()), 2);
  446.         $quantityToAllocate round($quantityToAllocate2);
  447.         if ($quantityToAllocate $unAllocatedQuantityFromProduct) {
  448.             return $this->json([
  449.                 'message' => sprintf(
  450.                     'Hatalı bilgi. Tahsis etmek istediğiniz miktar tahsis edilmemiş miktardan büyük olamaz. Tahsis edilmemiş miktar = %s',
  451.                     $productSold->getUnAllocatedQuantity()
  452.                 )
  453.             ], 400);
  454.         }
  455.         return $this->json(
  456.             ['message' => $this->salesProductsServiceImpl->allocateQuantity($productSold$quantityToAllocate)]
  457.         );
  458.     }
  459.     #[Route('/sales/un-allocated-products/detail/ajax'name'un_allocated_products_detail_ajax')]
  460.     public function getUnAllocatedProduct(Request $request)
  461.     {
  462.         /**
  463.          * @var ProductsSold $entity
  464.          */
  465.         $entity $this->salesProductsServiceImpl->getEntityById($request->get('productSoldId'));
  466.         return $this->json([
  467.             'id' => $entity->getId(),
  468.             'productName' => $entity->getProduct()->getName(),
  469.             'productCode' => $entity->getProduct()->getCode(),
  470.             'measurement' => $entity->getProduct()->getMeasurement()->getName(),
  471.             'measurementUnit' => $entity->getProduct()->getMeasurementUnit()->getSymbol(),
  472.             'customer' => $entity->getSales()->getCustomer()->getFullName(),
  473.             'allocatedQuantity' => $entity->getQuantity(),
  474.             'unAllocatedQuantity' => $entity->getUnAllocatedQuantity(),
  475.             'salesDate' => $entity->getSales()->getSalesDate()->format('d-m-Y'),
  476.             'warehouseName' => $entity->getSales()->getWarehouse()->getName(),
  477.             'stockInfo' => $this->stockInfoService->getStockInfoByProductAndWarehouse(
  478.                 $entity->getProduct(),
  479.                 $entity->getSales()->getWarehouse()
  480.             )->getTotalQuantity(),
  481.             'sellerName' => $entity->getSales()->getSeller()->getFullname(),
  482.             'totalSold' => CurrencyHelper::convertToCurrency($entity->getQuantity() + ($entity->getUnAllocatedQuantity() == null $entity->getUnAllocatedQuantity())),
  483.         ]);
  484.     }
  485.     #[Route('/proforma/index'name'proforma_index')]
  486.     public function getAllProformas(Request $requestEntityManagerInterface $entityManager)
  487.     {
  488.         $warehouses $this->warehouseService->getAll();
  489.         return $this->render('admin/sales/proforma_index.html.twig', [
  490.             'controller_name' => 'StockController',
  491.             'warehouses' => $warehouses
  492.         ]);
  493.     }
  494.     #[Route('/proforma/ajax'name'proforma_ajax')]
  495.     public function getAllProformasAjax(Request $requestSalesServiceImpl $salesServiceImpl)
  496.     {
  497.         $user $this->getUser();
  498.         if (isset($request->get('search')['value']))
  499.             $s $request->get('search')['value'];
  500.         else
  501.             $s '';
  502.         $id $request->get('id');
  503.         if (isset($request->get('order')[0]['column']))
  504.             $column $request->get('order')[0]['column'];
  505.         else
  506.             $column 0;
  507.         if (isset($request->get('order')[0]['dir']))
  508.             $dir $request->get('order')[0]['dir'];
  509.         else
  510.             $dir 'ASC';
  511.         $startDate $request->get('startDate');
  512.         $finishDate $request->get('finishDate');
  513.         $limit $request->get('length');
  514.         if ($request->get('start'))
  515.             $page + ($request->get('start') / $limit);
  516.         else
  517.             $page 1;
  518.         $warehouseid $request->get('warehouseid');
  519.         $stocks $salesServiceImpl->getAllProformasPaginate($user$page$limit$warehouseid$column$dir$s$startDate$finishDate);
  520.         return new JsonResponse($this->createArrayForDataTable($stocks));
  521.     }
  522.     private function createPayments($payments$customer$sales)
  523.     {
  524.         foreach ($payments as $payment) {
  525.             $paymentMethod $this->paymentMethodService->getPaymentMethodById($payment['paymentMethod']);
  526.             $paymentObj $this->paymentService->createPayment($customer$paymentMethod$sales$payment['paymentDate'], $payment['amount'], $payment['status'], $payment['paymentDueDate'], $payment['description']);
  527.             $a 0;
  528.             if ($paymentObj->getPaymentStatus() == 2) {
  529.                 $paymentReminder $this->paymentReminderService->create($paymentObj$payment['paymentDate'], $payment['status'], $payment['paymentMethod']);
  530.             }
  531.         }
  532.     }
  533.     /**
  534.      * @throws StockNotAvaibleException
  535.      * @throws StockNotFoundException
  536.      */
  537.     private function checkStocks($soldList): array
  538.     {
  539.         $warehouseId $soldList[0]['warehouse'];
  540.         $quantities = [];
  541.         foreach ($soldList as $soldProduct) {
  542.             $productId $soldProduct['productid'];
  543.             // Ürün tipine göre stok kontrolünü atla (service & tree)
  544.             $product $this->productService->getEntityId($productId);
  545.             if ($product && $this->isStockExemptProductType($product->getProductTypeEnum())) {
  546.                 continue; // bu ürünlerde stok kontrolü yapılmaz
  547.             }
  548.             // Birim dönüşümü: selectedUnitId farklı ise temel birime çevirerek topla
  549.             $rawQuantity = (float) $soldProduct['quantity'];
  550.             $selectedUnitId $soldProduct['selectedUnitId'] ?? null;
  551.             if (!isset($quantities[$productId])) {
  552.                 $quantities[$productId] = 0.0;
  553.             }
  554.             if ($selectedUnitId) {
  555.                 // SalesServiceImpl içindeki dönüştürme mantığını kullan
  556.                 $converted $this->salesServiceImpl->getConvertedQuantity($product$selectedUnitId$rawQuantity);
  557.                 $quantities[$productId] += (float) $converted;
  558.             } else {
  559.                 $quantities[$productId] += $rawQuantity;
  560.             }
  561.         }
  562.         foreach ($quantities as $productId => $quantity) {
  563.             $this->stockInfoService->checkIsStockAvailable($quantity$productId$warehouseId);
  564.         }
  565.         return $quantities;
  566.     }
  567.     /**
  568.      * @throws Exception
  569.      */
  570.     private function applyRoundingRule(float $value): float
  571.     {
  572.         return round($valueself::MONEY_SCALEPHP_ROUND_HALF_UP);
  573.     }
  574.     /**
  575.      * @throws Exception
  576.      */
  577.     private function checkPaymentAndAmountIsEqual($payments$soldList)
  578.     {
  579.         $totalPriceRaw 0.0;
  580.         $totalAmountOfPayments 0.0;
  581.         foreach ($payments as $payment) {
  582.             $totalAmountOfPayments += CurrencyHelper::convertToFloat($payment['amount']);
  583.         }
  584.         foreach ($soldList as $soldProduct) {
  585.             $totalPriceRaw += $this->calculateTotalPrice($soldProduct);
  586.         }
  587.         $totalPrice $this->applyRoundingRule($totalPriceRaw);
  588.         $totalAmountOfPayments $this->applyRoundingRule($totalAmountOfPayments);
  589.         $diff abs($totalPrice $totalAmountOfPayments);
  590.         if ($diff 1) {
  591.             throw new Exception("Ödemeler ile ürünlerin fiyatları farklı. Toplam Ürün Fiyatı: " $totalPrice ", Toplam Ödeme: " $totalAmountOfPayments ", Fark: " $diff401);
  592.         }
  593.         return $totalPrice;
  594.     }
  595.     /**
  596.      * @throws Exception
  597.      */
  598.     private function calculateTotalPrice($soldProduct): float
  599.     {
  600.         $quantity CurrencyHelper::convertToFloat($soldProduct['quantity']);
  601.         $unAllocatedQuantity CurrencyHelper::convertToFloat($soldProduct['unAllocatedQuantity']);
  602.         $tax CurrencyHelper::convertToFloat($soldProduct['tax']);
  603.         $unitPrice CurrencyHelper::convertToFloat($soldProduct['unitPrice']);
  604.         $discount CurrencyHelper::convertToFloat($soldProduct['discount']);
  605.         $totalPurchasePrice CurrencyHelper::convertToFloat($soldProduct['totalPurchasePrice']);
  606.         $totalQuantity $quantity $unAllocatedQuantity;
  607.         $subtotal $totalQuantity $unitPrice;
  608.         $discountAmount $subtotal * ($discount 100);
  609.         $afterDiscount $subtotal $discountAmount;
  610.         $taxAmount $afterDiscount * ($tax 100);
  611.         $lineTotal $afterDiscount $taxAmount;
  612.         $roundedLineTotal $this->applyRoundingRule($lineTotal);
  613.         $roundedFrontend $this->applyRoundingRule($totalPurchasePrice);
  614.         $difference abs($roundedLineTotal $roundedFrontend);
  615.         $rawDifference abs($lineTotal $totalPurchasePrice);
  616.         if ($difference 1) {
  617.             throw new Exception("Fiyatlar arasında hesaplamada fark var. Backend: " $roundedLineTotal ", Frontend: " $totalPurchasePrice ", Fark: " $difference401);
  618.         }
  619.         $this->logger->info("sales", [
  620.             'title' => 'Kullanıcının girdiği ödemeler ile ürün bazlı toplamlar karşılaştırılıyor',
  621.             'backend_rounded' => $roundedLineTotal,
  622.             'backend_raw' => $lineTotal,
  623.             'frontend' => $totalPurchasePrice,
  624.             'frontend_rounded' => $roundedFrontend,
  625.             'difference' => $difference,
  626.             'raw_difference' => $rawDifference,
  627.         ]);
  628.         return $lineTotal;
  629.     }
  630.     private function roundUpToTwoDecimalsPrecise($number)
  631.     {
  632.         return round(ceil($number 1000) / 10002);
  633.     }
  634.     #[Route('/product-calculate-cost'name'product_calculate_cost')]
  635.     public function exampleCostCalcolator(Request $requestStockRepository $stockRepository)
  636.     {
  637.         $repo $stockRepository->calculateCost(30);
  638.         return $this->json(['data' => 'data']);
  639.     }
  640.     #[Route('/stock-detail/{id}'name'app_admin_stock_detail')]
  641.     public function stockDetail(Request $request$id)
  642.     {
  643.         $stock $this->stockService->getStockById($id);
  644.         return $this->json($stock);
  645.     }
  646.     /**
  647.      * @throws Exception
  648.      */
  649.     #[Route('/product/{id}/measurement-units'name'get_product_measurement_units'methods: ['GET'])]
  650.     public function getAvailableMeasurementUnits(int $id): JsonResponse
  651.     {
  652.         $product $this->productService->getEntityId($id);
  653.         if (!$product)
  654.             throw new Exception(sprintf("Product not found with %s"$id), 404);
  655.         $availableUnits $this->measurementConversionsService->getAvailableUnitsFor($product);
  656.         //        dd($availableUnits);
  657.         return $this->json(
  658.             $availableUnits,
  659.             JsonResponse::HTTP_OK,
  660.             [],
  661.             ['groups' => 'measurement:read']
  662.         );
  663.     }
  664.     #[Route('/sales/detail/{id}'name'app_admin_sales_detail')]
  665.     public function edit(Request $request$idSalesRepository $salesRepository)
  666.     {
  667.         $payments $request->get('payments');
  668.         $soldList $request->get('soldList');
  669.         $oldStatus $this->salesService->getEntityById($id)->getStatus();
  670.         $newStatus $request->get('invoice')['salesStatus'] ?? null;
  671.         $sales $this->salesServiceImpl->getEntityById(3);
  672.         if (isset($soldList[0]['warehouse']))
  673.             $warehouseId $soldList[0]['warehouse'];
  674.         else
  675.             $warehouseId 0;
  676.         $invoice $request->get('invoice');
  677.         // Log the edit operation
  678.         $this->logger->info("sales", [
  679.             'title' => 'Satış düzenleme işlemi başlatıldı',
  680.             'saleId' => $id,
  681.             'request' => $request->request->all(),
  682.         ]);
  683.         $this->entityManager->beginTransaction();
  684.         try {
  685.             $sale $this->salesService->getEntityById($id);
  686.             $form $this->createForm(SalesFormType::class, $sale);
  687.             $soldProducts = new ProductsSold();
  688.             $soldProductsFrom $this->createForm(ProductsSoldType::class, $soldProducts);
  689.             $form->handleRequest($request);
  690.             $customer = new Customer();
  691.             $customerForm $this->createForm(CustomerType::class, $customer);
  692.             $customerForm->handleRequest($request);
  693.             if ($customerForm->isSubmitted() && $customerForm->isValid()) {
  694.                 /**
  695.                  * @var Customer $customer
  696.                  */
  697.                 $customer $customerForm->getData();
  698.                 $this->addFlash('success''Müşteri başarıyla eklendi. Listeden seçebilirsiniz.');
  699.                 $email $customer->getEmail();
  700.                 $customer->setEmail(mb_strtolower($email'UTF-8'));
  701.                 $this->customerService->save($customerForm->getData());
  702.                 $this->entityManager->commit();
  703.                 $referer $request->headers->get('referer');
  704.                 return $this->redirect($referer ?: '/sales/detail/' $id);
  705.             }
  706.             $payment = new Payment();
  707.             $paymentForm $this->createForm(PaymentType::class, $payment);
  708.             $paymentMethods $this->paymentMethodService->getPaymentMethods();
  709.             $warehouses $this->warehouseService->getAll();
  710.             if ($request->getMethod() == 'POST') {
  711.                 $sale $this->salesService->getEntityById($id);
  712.                 $salesType $invoice['salesType'] ?? 'PROFORMA'// Default to PROFORMA if not provided
  713.                 $groupedTotalQuantitiesByProduct $salesRepository->getGroupedProductsSoldBySale($sale);
  714.                 // Stok kontrolü: yalnızca COMPLETED veya PENDING_ITEMS durumunda yapılır
  715.                 $intendedStatus $this->resolveSalesStatus($invoice['salesStatus'] ?? null);
  716.                 if ($this->shouldAffectStock($intendedStatus)) {
  717.                     $this->checkStocksIsAvailableForEditedSale($soldList$groupedTotalQuantitiesByProduct);
  718.                 }
  719.                 // Ürün bazlı indirim kdv gibi işlemler yapılıp total fiyatların frontend ile aynı olup olmadığı kontrol ediliyor.
  720.                 foreach ($soldList as $soldProduct) {
  721.                     $this->logger->info("sales", [
  722.                         'title' => 'Ürün bazlı indirim kdv hesaplanıyor (düzenleme)...',
  723.                         'product' => $soldProduct,
  724.                     ]);
  725.                     $this->assertUnitPriceAllowed($soldProduct);
  726.                     $this->calculateTotalPrice($soldProduct);
  727.                 }
  728.                 // Ödeme planı ile ürünlerin toplam fiyatlarının eşitliği kontrol ediliyor.
  729.                 $totalPrice $this->checkPaymentAndAmountIsEqual($payments$soldList);
  730.                 $repo $this->entityManager->getRepository(Sales::class);
  731.                 $saleQ $repo->createQueryBuilder('c')
  732.                     ->where('c.invoiceNumber = :invoiceNumber')
  733.                     ->andWhere('c.id != :id')
  734.                     ->setParameters(['invoiceNumber' => $invoice['invoiceNumber'], 'id' => $id])
  735.                     ->getQuery()
  736.                     ->getResult();
  737.                 if ($saleQ) {
  738.                     throw new Exception(sprintf("%s fatura numarasına kayıtlı bir satış mevcut."$invoice['invoiceNumber']));
  739.                 } else {
  740.                     $soldProductsArray = new ArrayCollection();
  741.                     for ($i 0$i count($soldList); $i++) {
  742.                         $product $this->productService->getEntityId($soldList[$i]['productid']);
  743.                         $calculatorDto $this->stockRepository->calculateCost($soldList[$i]['productid'], $warehouseId);
  744.                         $builder = new SalesProductsDtoBuilder();
  745.                         $soldProductsArray->add(
  746.                             $builder
  747.                                 ->setProductName($soldList[$i]['productName'] ?? $product->getName())
  748.                                 ->setMeasurement($product->getMeasurement()?->getMeasurement())
  749.                                 ->setQuantity($soldList[$i]['quantity'])
  750.                                 ->setUnitPriceFob($calculatorDto->getPriceFob())
  751.                                 ->setUnitPriceNavlun($calculatorDto->getPriceNavlun())
  752.                                 ->setTotalUnitPrice($soldList[$i]['unitPrice'])
  753.                                 ->setTotalUnitPurchasePrice($soldList[$i]['totalPurchasePrice'])
  754.                                 ->setProductId($product->getId())
  755.                                 ->setWarehouseId($warehouseId)
  756.                                 ->setTax($soldList[$i]['tax'])
  757.                                 ->setTotalSalePrice($soldList[$i]['totalPurchasePrice'])
  758.                                 ->setDiscount($soldList[$i]['discount'])
  759.                                 ->setUnAllocatedQuantity($soldList[$i]['unAllocatedQuantity'])
  760.                                 ->setSelectedUnitId($soldList[$i]['selectedUnitId'] ?? null)
  761.                                 ->build()
  762.                         );
  763.                     }
  764.                     $customer $this->customerService->getCustomerById($invoice['customer']);
  765.                     $sale->setSalesDate(new \DateTime($invoice['salesDate']));
  766.                     $sale->setCustomer($customer);
  767.                     $userRepo $this->entityManager->getRepository(User::class);
  768.                     $sale->setSeller($userRepo->find($invoice['seller']));
  769.                     $sale->setInvoiceNumber($invoice['invoiceNumber']);
  770.                     $sale->setTotalPurchasePrice($totalPrice);
  771.                     $sale->setDescription($invoice['description']);
  772.                     $sale->setShippingNotes($invoice['shippingNotes']);
  773.                     $sale->setPaymentNotes($invoice['paymentNotes']);
  774.                     $performedBy $this->getUser();
  775.                     $sale $this->salesService->update(
  776.                         $sale,
  777.                         $soldProductsArray,
  778.                         $intendedStatus,
  779.                         $performedBy instanceof User $performedBy null
  780.                     );
  781.                     $this->createPayments($payments$customer$sale);
  782.                     $this->entityManager->commit();
  783.                     return new JsonResponse(['success' => true]);
  784.                 }
  785.             }
  786.         } catch (Exception $exception) {
  787.             $this->entityManager->rollback();
  788.             $message $exception->getMessage();
  789.             $this->logger->error("sales", [
  790.                 'title' => 'Satış düzenleme hatası',
  791.                 'error' => $message,
  792.                 'trace' => $exception->getTraceAsString()
  793.             ]);
  794.             return new JsonResponse(['status' => 'error''message' => $message], 400);
  795.         }
  796.         $historyEntries $sale->getHistoryEntries()->toArray();
  797.         usort(
  798.             $historyEntries,
  799.             static fn(SalesHistory $aSalesHistory $b) => $b->getPerformedAt()->getTimestamp() <=> $a->getPerformedAt()->getTimestamp()
  800.         );
  801.         $returns $this->salesReturnRepository->findBy(['sale' => $sale], ['requestedAt' => 'DESC']);
  802.         // Check if returns can be created for this sale
  803.         $canCreateReturn $this->canCreateReturnForSale($sale);
  804.         return $this->render('admin/sales/edit.html.twig', [
  805.             'sale' => $sale,
  806.             'form' => $form->createView(),
  807.             'warehouses' => $warehouses,
  808.             'productsSoldForm' => $soldProductsFrom->createView(),
  809.             'customerForm' => $customerForm->createView(),
  810.             'paymentForm' => $paymentForm->createView(),
  811.             'paymentMethods' => $paymentMethods,
  812.             'historyEntries' => $historyEntries,
  813.             'returns' => $returns,
  814.             'canCreateReturn' => $canCreateReturn,
  815.             'defaultMailContent' => $this->getDefaultMailContent(),
  816.         ]);
  817.     }
  818.     public function checkStocksIsAvailableForEditedSale($productToSolds$soldQuantities)
  819.     {
  820.         $newProductSoldArr = [];
  821.         $warehouse $productToSolds[0]['warehouse'];
  822.         foreach ($productToSolds as $productToSold) {
  823.             $productId $productToSold['productid'];
  824.             // Ürün tipine göre stok kontrolünü atla (service & tree)
  825.             $product $this->productService->getEntityId($productId);
  826.             if ($product && $this->isStockExemptProductType($product->getProductTypeEnum())) {
  827.                 continue; // bu ürünlerde stok kontrolü yapılmaz
  828.             }
  829.             // Birim dönüşümü: selectedUnitId farklı ise temel birime çevirerek topla
  830.             $rawQuantity = (float) $productToSold['quantity'];
  831.             $selectedUnitId $productToSold['selectedUnitId'] ?? null;
  832.             if (!isset($newProductSoldArr[$productId])) {
  833.                 $newProductSoldArr[$productId] = 0.0;
  834.             }
  835.             if ($selectedUnitId) {
  836.                 // SalesServiceImpl içindeki dönüştürme mantığını kullan
  837.                 $converted $this->salesServiceImpl->getConvertedQuantity($product$selectedUnitId$rawQuantity);
  838.                 $newProductSoldArr[$productId] += (float) $converted;
  839.             } else {
  840.                 $newProductSoldArr[$productId] += $rawQuantity;
  841.             }
  842.         }
  843.         foreach ($newProductSoldArr as $productId => $requiredQuantity) {
  844.             $availableStock $this->stockInfoService->getStockInfoByProductAndWarehouse(
  845.                 $this->productService->getEntityId($productId),
  846.                 $this->warehouseService->getEntityById($warehouse)
  847.             );
  848.             $previouslySoldQuantity $this->findStockInArray($soldQuantities$productId);
  849.             $totalAvailableStock $previouslySoldQuantity $availableStock->getTotalQuantity();
  850.             if ($requiredQuantity $totalAvailableStock) {
  851.                 throw new Exception(sprintf(
  852.                     "Yetersiz stok. %s id'li ürün için toplam kullanılabilir stok miktarı = %s, talep edilen = %s",
  853.                     $productId,
  854.                     $totalAvailableStock,
  855.                     $requiredQuantity
  856.                 ));
  857.             }
  858.         }
  859.         return true;
  860.     }
  861.     public function findStockInArray($arr$productId)
  862.     {
  863.         foreach ($arr as $item) {
  864.             if ($item['productId'] == $productId) {
  865.                 return $item['totalQuantity'];
  866.             }
  867.         }
  868.         return 0;
  869.     }
  870.     private function normalizeProductTypeName($type): ?string
  871.     {
  872.         if ($type instanceof \UnitEnum) {
  873.             return $type->name;
  874.         }
  875.         if (is_array($type)) {
  876.             return $type['name'] ?? ($type['key'] ?? null);
  877.         }
  878.         if (is_string($type)) {
  879.             return strtoupper($type);
  880.         }
  881.         return null;
  882.     }
  883.     private function isStockExemptProductType($type): bool
  884.     {
  885.         $normalized $this->normalizeProductTypeName($type);
  886.         return $normalized in_array($normalizedself::STOCK_EXEMPT_PRODUCT_TYPEStrue) : false;
  887.     }
  888.     private function canProductTypeUseNegativePrice($type): bool
  889.     {
  890.         $normalized $this->normalizeProductTypeName($type);
  891.         return $normalized in_array($normalizedself::NEGATIVE_PRICE_ALLOWED_PRODUCT_TYPEStrue) : false;
  892.     }
  893.     private function assertUnitPriceAllowed(array $soldProduct): void
  894.     {
  895.         if (empty($soldProduct['productid']) || !array_key_exists('unitPrice'$soldProduct)) {
  896.             return;
  897.         }
  898.         $product $this->productService->getEntityId($soldProduct['productid']);
  899.         if (!$product) {
  900.             return;
  901.         }
  902.         $unitPrice CurrencyHelper::convertToFloat($soldProduct['unitPrice']);
  903.         $type $product->getProductTypeEnum();
  904.         if ($this->canProductTypeUseNegativePrice($type)) {
  905.             if ($unitPrice >= 0) {
  906.                 throw new Exception('Avoir ürünlerinde birim fiyat negatif olmalıdır.');
  907.             }
  908.             return;
  909.         }
  910.     }
  911.     private function resolveSalesStatus(?string $status): SalesStatus
  912.     {
  913.         if ($status === null) {
  914.             return SalesStatus::PENDING;
  915.         }
  916.         return SalesStatus::tryFrom($status) ?? SalesStatus::PENDING;
  917.     }
  918.     private function shouldAffectStock(SalesStatus $status): bool
  919.     {
  920.         return in_array($status, [SalesStatus::COMPLETEDSalesStatus::PENDING_ITEMS], true);
  921.     }
  922.     /**
  923.      * Check if returns can be created for this sale
  924.      * Returns can only be created for:
  925.      * 1. Sales with COMPLETED or PENDING_ITEMS status
  926.      * 2. Sales that have at least one physical product (excluding services, trees)
  927.      */
  928.     private function canCreateReturnForSale(Sales $sale): bool
  929.     {
  930.         // Check sale status
  931.         $saleStatus $sale->getStatus();
  932.         if (!$saleStatus || !in_array($saleStatus, [SalesStatus::COMPLETEDSalesStatus::PENDING_ITEMS], true)) {
  933.             return false;
  934.         }
  935.         // Check if there are any physical products
  936.         $productsSold $sale->getProductsSolds();
  937.         foreach ($productsSold as $productSold) {
  938.             if (!$productSold instanceof ProductsSold) {
  939.                 continue;
  940.             }
  941.             $product $productSold->getProduct();
  942.             if (!$product) {
  943.                 continue;
  944.             }
  945.             $type $product->getProductTypeEnum();
  946.             if (!$this->isStockExemptProductType($type)) {
  947.                 return true// Found at least one physical product
  948.             }
  949.         }
  950.         return false// No physical products found
  951.     }
  952.     #[Route('/delete-sales/{id}'name'delete-sales')]
  953.     public function delete(Request $request$idEntityManagerInterface $entityManager)
  954.     {
  955.         $sale $entityManager->getRepository(Sales::class)->find($id);
  956.         if (!$sale) {
  957.             throw $this->createNotFoundException('Satış bulunamadı.');
  958.         }
  959.         $performedBy $this->getUser();
  960.         $sale->setVisible(false);
  961.         $this->salesHistoryService->record(
  962.             $sale,
  963.             SalesHistoryEventType::VISIBILITY_CHANGED,
  964.             'Satış gizlendi (soft delete).',
  965.             ['visible' => false],
  966.             $performedBy instanceof User $performedBy null
  967.         );
  968.         $entityManager->persist($sale);
  969.         $entityManager->flush();
  970.         $this->addFlash('success''Satış Başarıyla silindi. Stoklar eklendi');
  971.         return $this->redirectToRoute('app_admin_sales');
  972.         /*
  973.         $sale = $entityManager->getRepository(Sales::class)->find($id);
  974.         $entityManager->beginTransaction();
  975.         try{
  976.             if($sale->getInvoice() != null){
  977.                 $this->invoiceService->deleteInvoice($sale->getInvoice());
  978.             }
  979.             if($sale->getStatus() === SalesStatus::PENDING_ITEMS || $sale->getStatus() === SalesStatus::COMPLETED){
  980.                 $warehouse = $sale->getWarehouse();
  981.                 foreach ($sale->getProductsSolds() as $salesProductsDto) {
  982.                         $productEntity = $this->productService->getEntityId($salesProductsDto->getProduct()->getId());
  983.                         $beforeQuantity = $this->stockInfoService->getStockInfoByProductAndWarehouse($productEntity, $warehouse)->getTotalQuantity();
  984.                         $stockTransaction = new StockTransaction();
  985.                         $stockTransaction->setWarehouse($warehouse);
  986.                         $stockTransaction->setProductId($productEntity);
  987.                         $stockTransaction->setName($productEntity->getName() . " Code " . $productEntity->getCode());
  988.                         $stockTransaction->setQuantity($salesProductsDto->getQuantity());
  989.                         $stockTransaction->setAction("IN");
  990.                         $stockTransaction->setDescription("Satış siliyor bu yüzden ürün stoğu EKLENİYOR");
  991.                         $stockTransaction->setBeforeQuantity(
  992.                             $beforeQuantity
  993.                         );
  994.                         $this->stockInfoService->addStock( $productEntity, $warehouse,$salesProductsDto->getQuantity());
  995.                         $stockTransaction->setAfterQuantity(
  996.                             $this->stockInfoService->getStockInfoByProductAndWarehouse($productEntity, $warehouse)->getTotalQuantity()
  997.                         );
  998.                         $stockTransaction->setSale($sale);
  999.                         $this->entityManager->remove($salesProductsDto);
  1000.                         $this->entityManager->persist($stockTransaction);
  1001.                     }
  1002.             }else{
  1003.                 $products = $sale->getProductsSolds();
  1004.                 foreach ($products as $product){
  1005.                     $this->entityManager->remove($product);
  1006.                 }
  1007.             }
  1008.             $payments = $sale->getPayments();
  1009.             foreach ($payments as $payment){
  1010.                 $this->paymentService->deletePayment($payment);
  1011.             }
  1012.             $entityManager->remove($sale);
  1013.             $entityManager->flush();
  1014.             $entityManager->commit();
  1015.             $this->addFlash('success', 'Satış Başarıyla silindi. Stoklar eklendi');
  1016.             return $this->redirectToRoute('app_admin_sales');
  1017.         }catch (\Exception $exception){
  1018.             $entityManager->rollback();
  1019.             throw $exception;
  1020.         }
  1021.         */
  1022.     }
  1023.     #[Route('/warehouse-stocks/{warehouseId}'name'app_admin_warehouse_stock_information')]
  1024.     public function getStockByWarehouse(Request $request$warehouseId)
  1025.     {
  1026.         $stocks $this->stockService->getStocksByWarehouse($warehouseId);
  1027.         return $this->json($stocks);
  1028.     }
  1029.     // Tüm satışlar anasayfa
  1030.     #[Route('/sales/all'name'app_admin_sales_all')]
  1031.     public function showAll(Request $requestEntityManagerInterface $entityManager)
  1032.     {
  1033.         $warehouses $this->warehouseService->getAll();
  1034.         return $this->render('admin/sales/allsales.html.twig', [
  1035.             'controller_name' => 'StockController',
  1036.             'warehouses' => $warehouses,
  1037.             'statuses' => SalesStatus::cases(),
  1038.             'defaultMailContent' => $this->getDefaultMailContent(),
  1039.         ]);
  1040.     }
  1041.     // Tüm satışlar ajax ile dataların gönderiliyor
  1042.     #[Route('/admin/sales/show'name'app_admin_sales_show')]
  1043.     public function show(Request $requestEntityManagerInterface $entityManager)
  1044.     {
  1045.         if (isset($request->get('search')['value']))
  1046.             $s $request->get('search')['value'];
  1047.         else
  1048.             $s '';
  1049.         $id $request->get('id');
  1050.         if (isset($request->get('order')[0]['column']))
  1051.             $column $request->get('order')[0]['column'];
  1052.         else
  1053.             $column 0;
  1054.         if (isset($request->get('order')[0]['dir']))
  1055.             $dir $request->get('order')[0]['dir'];
  1056.         else
  1057.             $dir 'ASC';
  1058.         $startDate $request->get('startDate');
  1059.         $finishDate $request->get('finishDate');
  1060.         $limit $request->get('length');
  1061.         if ($request->get('start'))
  1062.             $page + ($request->get('start') / $limit);
  1063.         else
  1064.             $page 1;
  1065.         $warehouseid $request->get('warehouseid');
  1066.         $statusParam $request->get('status');
  1067.         $status null;
  1068.         if (!empty($statusParam)) {
  1069.             try {
  1070.                 $status SalesStatus::from($statusParam);
  1071.             } catch (\ValueError $exception) {
  1072.                 $status null;
  1073.             }
  1074.         }
  1075.         $stocks $this->salesService->getAllSalesPaginate($page$limit$warehouseid$column$dir$s$startDate$finishDate$status$this->getUser());
  1076.         return new JsonResponse($this->createArrayForDataTable($stocks));
  1077.     }
  1078.     private function getPdfContentFromProforma($proformaId)
  1079.     {
  1080.         $logoPath $this->projectDir '/public/images/decopierrelogo.png';
  1081.         $logoDataUri '';
  1082.         if (file_exists($logoPath)) {
  1083.             $logoBase64 base64_encode(file_get_contents($logoPath));
  1084.             $logoDataUri 'data:' mime_content_type($logoPath) . ';base64,' $logoBase64;
  1085.         }
  1086.         $sales $this->salesServiceImpl->getEntityById($proformaId);
  1087.         if (!$sales) {
  1088.             return null// Fatura bulunamazsa null döndür
  1089.         }
  1090.         $companyData = [
  1091.             'name' => 'DECO PIERRE ET NATURE',
  1092.             'address' => '152 ROUTE DE GRENOBLE 69800 SAINT PRIEST, France',
  1093.             'siren' => '948485271',
  1094.             'tva' => 'FR13948485271',
  1095.             'contact' => 'MATHIAS | 0652901882 | decopierreetnature@gmail.com',
  1096.             'bank_details' => [
  1097.                 'titulaire' => 'SAS FAV',
  1098.                 'bank_name' => 'BANQUE POPULAIRE',
  1099.                 'code_banque' => '16807',
  1100.                 'num_compte' => '37402602218',
  1101.                 'iban' => 'FR7616807004133740260221837',
  1102.                 'bic' => 'CCBPFRPPGRE'
  1103.             ]
  1104.         ];
  1105.         $html $this->renderView('/admin/sales/proforma.html.twig', [
  1106.             'proforma' => $sales,
  1107.             'logo_data_uri' => $logoDataUri,
  1108.             'company' => $companyData
  1109.         ]);
  1110.         $footerHtml $this->renderView('/admin/sales/invoice_footer.html.twig', [
  1111.             'company' => $companyData
  1112.         ]);
  1113.         $options = [
  1114.             'footer-html' => $footerHtml,
  1115.             'margin-bottom' => 30,
  1116.             'enable-local-file-access' => true,
  1117.             'encoding' => 'UTF-8',
  1118.         ];
  1119.         return [
  1120.             'html' => $html,
  1121.             'options' => $options,
  1122.         ];
  1123.     }
  1124.     private function getDefaultMailContent(): string
  1125.     {
  1126.         return MailTemplateHelper::getDefaultMailContent();
  1127.     }
  1128.     private function applyMailPlaceholders(string $mailContentSales $sale): string
  1129.     {
  1130.         $customer $sale->getCustomer();
  1131.         $placeholders = [
  1132.             '[[client_name]]' => $customer ? (string) $customer->getFullName() : '',
  1133.             '[[client_email]]' => $customer && $customer->getEmail() ? (string) $customer->getEmail() : '',
  1134.             '[[client_phone]]' => $customer && $customer->getPhone() ? (string) $customer->getPhone() : '',
  1135.         ];
  1136.         foreach ($placeholders as $placeholder => $value) {
  1137.             $mailContent str_replace($placeholder$value ?? ''$mailContent);
  1138.         }
  1139.         return $mailContent;
  1140.     }
  1141.     private function getPdfContentFromInvoice(int $invoiceId): ?array
  1142.     {
  1143.         $logoPath $this->projectDir '/public/images/decopierrelogo.png';
  1144.         $logoDataUri '';
  1145.         if (file_exists($logoPath)) {
  1146.             $logoBase64 base64_encode(file_get_contents($logoPath));
  1147.             $logoDataUri 'data:' mime_content_type($logoPath) . ';base64,' $logoBase64;
  1148.         }
  1149.         $invoice $this->invoiceService->getEntityById($invoiceId);
  1150.         if (!$invoice) {
  1151.             return null;
  1152.         }
  1153.         $companyData = [
  1154.             'name' => 'DECO PIERRE ET NATURE',
  1155.             'address' => '152 ROUTE DE GRENOBLE 69800 SAINT PRIEST, France',
  1156.             'siren' => '948485271',
  1157.             'tva' => 'FR13948485271',
  1158.             'contact' => 'MATHIAS | 0652901882 | decopierreetnature@gmail.com',
  1159.             'bank_details' => [
  1160.                 'titulaire' => 'SAS FAV',
  1161.                 'bank_name' => 'BANQUE POPULAIRE',
  1162.                 'code_banque' => '16807',
  1163.                 'num_compte' => '37402602218',
  1164.                 'iban' => 'FR7616807004133740260221837',
  1165.                 'bic' => 'CCBPFRPPGRE'
  1166.             ]
  1167.         ];
  1168.         $html $this->renderView('/admin/sales/invoice.html.twig', [
  1169.             'invoice' => $invoice,
  1170.             'logo_data_uri' => $logoDataUri,
  1171.             'company' => $companyData
  1172.         ]);
  1173.         $footerHtml $this->renderView('/admin/sales/invoice_footer.html.twig', [
  1174.             'company' => $companyData
  1175.         ]);
  1176.         $options = [
  1177.             'footer-html' => $footerHtml,
  1178.             'margin-bottom' => 30,
  1179.             'enable-local-file-access' => true,
  1180.             'encoding' => 'UTF-8',
  1181.         ];
  1182.         return [
  1183.             'html' => $html,
  1184.             'options' => $options,
  1185.         ];
  1186.     }
  1187.     #[Route('/proforma/send-mail/{proformaId}'name'proforma_send_mail'methods: ['POST'])]
  1188.     public function sendMailToCustomer(Request $request$proformaIdMailService $mailService)
  1189.     {
  1190.         $sale $this->salesService->getEntityById($proformaId);
  1191.         if (!$sale) {
  1192.             return new JsonResponse(['message' => 'sale_not_found'], 404);
  1193.         }
  1194.         $data json_decode($request->getContent(), true);
  1195.         if (json_last_error() !== JSON_ERROR_NONE || !is_array($data)) {
  1196.             $data $request->request->all();
  1197.         }
  1198.         $recipientsRaw $data['recipients'] ?? [];
  1199.         if (!is_array($recipientsRaw)) {
  1200.             $recipientsRaw explode(',', (string) $recipientsRaw);
  1201.         }
  1202.         if (empty($recipientsRaw) && $sale->getCustomer() && $sale->getCustomer()->getEmail()) {
  1203.             $recipientsRaw = [$sale->getCustomer()->getEmail()];
  1204.         }
  1205.         $recipients = [];
  1206.         foreach ($recipientsRaw as $recipient) {
  1207.             $recipient mb_strtolower(trim((string) $recipient));
  1208.             if ($recipient && filter_var($recipientFILTER_VALIDATE_EMAIL)) {
  1209.                 $recipients[] = $recipient;
  1210.             }
  1211.         }
  1212.         $recipients array_values(array_unique($recipients));
  1213.         if (!$recipients) {
  1214.             return new JsonResponse(['message' => 'recipient_not_found'], 400);
  1215.         }
  1216.         $mailContent $data['content'] ?? $this->getDefaultMailContent();
  1217.         if (!$mailContent) {
  1218.             $mailContent $this->getDefaultMailContent();
  1219.         }
  1220.         $mailContent $this->applyMailPlaceholders($mailContent$sale);
  1221.         $includeProforma array_key_exists('includeProforma'$data)
  1222.             ? filter_var($data['includeProforma'], FILTER_VALIDATE_BOOLEAN)
  1223.             : true;
  1224.         $includeInvoice array_key_exists('includeInvoice'$data)
  1225.             ? filter_var($data['includeInvoice'], FILTER_VALIDATE_BOOLEAN)
  1226.             : ($sale->getInvoice() !== null);
  1227.         $attachments = [];
  1228.         if ($includeProforma) {
  1229.             $proformaContent $this->getPdfContentFromProforma($proformaId);
  1230.             if ($proformaContent) {
  1231.                 $proformaPath $this->pdfService->convertAndSave(
  1232.                     $proformaContent['html'],
  1233.                     $sale->getInvoiceNumber(),
  1234.                     'proforma',
  1235.                     $proformaContent['options']
  1236.                 );
  1237.                 if ($proformaPath && file_exists($proformaPath)) {
  1238.                     $attachments[] = $proformaPath;
  1239.                 }
  1240.             }
  1241.         }
  1242.         if ($includeInvoice && $sale->getInvoice()) {
  1243.             $invoiceContent $this->getPdfContentFromInvoice($sale->getInvoice()->getId());
  1244.             if ($invoiceContent) {
  1245.                 $invoicePath $this->pdfService->convertAndSave(
  1246.                     $invoiceContent['html'],
  1247.                     $sale->getInvoice()->getInvoiceNumber(),
  1248.                     'invoice',
  1249.                     $invoiceContent['options']
  1250.                 );
  1251.                 if ($invoicePath && file_exists($invoicePath)) {
  1252.                     $attachments[] = $invoicePath;
  1253.                 }
  1254.             }
  1255.         }
  1256.         $subject = ($includeInvoice && $sale->getInvoice())
  1257.             ? 'Facture ' $sale->getInvoice()->getInvoiceNumber()
  1258.             : 'Devis ' $sale->getInvoiceNumber();
  1259.         //        dd($mailContent);
  1260.         $result $mailService->sendAdvancedMail([
  1261.             'to' => $recipients,
  1262.             'subject' => $subject,
  1263.             'html' => $mailContent,
  1264.             'bcc' => ['arsiv@decopierrenature.com'],
  1265.             'replyTo' => 'decopierrefatih@gmail.com',
  1266.             'attachments' => array_values(array_unique($attachments)),
  1267.             'sale' => $sale,
  1268.             'senderUser' => $this->getUser(),
  1269.         ]);
  1270.         $message $result 'success' 'error';
  1271.         return new JsonResponse(['message' => $message]);
  1272.     }
  1273.     /**
  1274.      * @throws Exception
  1275.      */
  1276.     #[Route('/proforma/convert-to-invoice/{salesId}'name'proforma_convert_to_invoice')]
  1277.     public function convertToInvoice(int $salesId): JsonResponse
  1278.     {
  1279.         $this->entityManager->beginTransaction();
  1280.         try {
  1281.             /** @var Sales|null $sales */
  1282.             $sales $this->entityManager->find(Sales::class, $salesIdLockMode::PESSIMISTIC_WRITE);
  1283.             if (!$sales) {
  1284.                 throw new Exception(sprintf("Proforma not found with %s id"$salesId));
  1285.             }
  1286.             if ($sales->getInvoice()) {
  1287.                 $invoiceId $sales->getInvoice()->getId();
  1288.                 $this->entityManager->commit();
  1289.                 return new JsonResponse(
  1290.                     [
  1291.                         'success' => false,
  1292.                         'message' => 'invoice_already_exists',
  1293.                         'invoiceId' => $invoiceId,
  1294.                     ],
  1295.                     Response::HTTP_CONFLICT
  1296.                 );
  1297.             }
  1298.             $invoice $this->invoiceService->create($sales);
  1299.             $this->entityManager->commit();
  1300.             return new JsonResponse(
  1301.                 [
  1302.                     'success' => true,
  1303.                     'invoiceId' => $invoice->getId(),
  1304.                 ],
  1305.                 Response::HTTP_CREATED
  1306.             );
  1307.         } catch (Exception $exception) {
  1308.             $this->entityManager->rollback();
  1309.             throw $exception;
  1310.         }
  1311.     }
  1312.     private function createArrayForDataTable($stocks): array
  1313.     {
  1314.         $records = [];
  1315.         $records['recordsTotal'] = $stocks->getTotalItemCount();
  1316.         $records['recordsFiltered'] = $stocks->getTotalItemCount();
  1317.         $records["data"] = [];
  1318.         /**
  1319.          * @var Sales $entity
  1320.          */
  1321.         foreach ($stocks as $entity) {
  1322.             $pdfLink '/admin/sales/proforma/generate-pdf/' $entity->getId();
  1323.             $printLink '/admin/sales/proforma/preview-print/' $entity->getId();
  1324.             $showProforma '/admin/sales/proforma/' $entity->getId();
  1325.             $customer $entity->getCustomer();
  1326.             $customerName $customer ? (string) $customer->getFullName() : '';
  1327.             $customerEmail $customer && $customer->getEmail() ? mb_strtolower((string) $customer->getEmail(), 'UTF-8') : '';
  1328.             $customerPhone $customer && $customer->getPhone() ? (string) $customer->getPhone() : '';
  1329.             $proformaPreviewUrl $this->generateUrl('admin_sales_proforma_preview_print', ['salesId' => $entity->getId()]);
  1330.             $invoicePreviewUrl $entity->getInvoice() ? $this->generateUrl('admin_sales_invoice_preview_print', ['invoiceId' => $entity->getInvoice()->getId()]) : '';
  1331.             $sendMailUrl $this->generateUrl('proforma_send_mail', ['proformaId' => $entity->getId()]);
  1332.             $sendMailAttributes sprintf(
  1333.                 'data-id="%s" data-sale-id="%s" data-send-url="%s" data-default-email="%s" data-customer-name="%s" data-customer-email="%s" data-customer-phone="%s" data-proforma-url="%s" data-invoice-url="%s" data-has-invoice="%s"',
  1334.                 htmlspecialchars((string) $entity->getId(), ENT_QUOTES ENT_SUBSTITUTE'UTF-8'),
  1335.                 htmlspecialchars((string) $entity->getId(), ENT_QUOTES ENT_SUBSTITUTE'UTF-8'),
  1336.                 htmlspecialchars($sendMailUrlENT_QUOTES ENT_SUBSTITUTE'UTF-8'),
  1337.                 htmlspecialchars($customerEmailENT_QUOTES ENT_SUBSTITUTE'UTF-8'),
  1338.                 htmlspecialchars($customerNameENT_QUOTES ENT_SUBSTITUTE'UTF-8'),
  1339.                 htmlspecialchars($customerEmailENT_QUOTES ENT_SUBSTITUTE'UTF-8'),
  1340.                 htmlspecialchars($customerPhoneENT_QUOTES ENT_SUBSTITUTE'UTF-8'),
  1341.                 htmlspecialchars($proformaPreviewUrlENT_QUOTES ENT_SUBSTITUTE'UTF-8'),
  1342.                 htmlspecialchars($invoicePreviewUrlENT_QUOTES ENT_SUBSTITUTE'UTF-8'),
  1343.                 $entity->getInvoice() ? '1' '0'
  1344.             );
  1345.             $actions sprintf(
  1346.                 '<a href="%s" target="_blank" title="Download PDF" style="margin-right: 8px;"><i class="far fa-file-pdf fa-lg" style="color: #d32f2f;"></i></a>
  1347.                         <a type="button" class="send-mail-btn" %s title="Send Mail" style="margin-right: 8px;"><i class="fas fa-mail-bulk fa-lg" style="color: #3498db;"></i></a>
  1348.                         <a href="%s" title="Print" target="_blank"><i class="fas fa-print fa-lg" style="color: #026309;"></i></a>
  1349.                         <a href="%s" title="Show" target="_blank"><i class="fas fa-eye fa-lg"></i></a>',
  1350.                 $pdfLink,
  1351.                 $sendMailAttributes,
  1352.                 $printLink,
  1353.                 $showProforma
  1354.             );
  1355.             $salesStatus $entity->getStatus();
  1356.             $statusLabel $salesStatus?->getLabel() ?? 'Belirtilmedi';
  1357.             $statusValue $salesStatus?->value ?? 'UNSPECIFIED';
  1358.             $statusCell sprintf(
  1359.                 "<span class='sales-status-badge' data-status='%s'>%s</span>",
  1360.                 htmlspecialchars($statusValueENT_QUOTES ENT_SUBSTITUTE'UTF-8'),
  1361.                 htmlspecialchars($statusLabelENT_QUOTES ENT_SUBSTITUTE'UTF-8')
  1362.             );
  1363.             $records["data"][] = [
  1364.                 $entity->getId(),
  1365.                 $entity->getInvoiceNumber(),
  1366.                 $customerName,
  1367.                 CurrencyHelper::convertToCurrency($entity->getTotalPurchasePrice()) . ' ' $this->translator->trans('currency.symbol'),
  1368.                 $entity->getSeller()->getFullname(),
  1369.                 $entity->getInvoice() ?
  1370.                 "<a target='_blank' href='" $this->generateUrl('admin_sales_invoice_preview_print', ['invoiceId' => $entity->getInvoice()->getId()]) . "' class='btn btn-primary btn-xs showInvoiceBtn'>" $this->translator->trans('showInvoice') . "</a>" :
  1371.                 "<a target='_blank' data-id='" $entity->getId() . "' class='btn btn-warning btn-xs convertToInvoiceBtn'>" $this->translator->trans('convertToInvoice') . "</a>",
  1372.                 $entity->getSalesDate()->format('d-m-Y'),
  1373.                 $statusCell,
  1374.                 $this->getPaymentStatus($entity),
  1375.                 $actions
  1376.             ];
  1377.         }
  1378.         return $records;
  1379.     }
  1380.     private function getPaymentStatus(Sales $sales)
  1381.     {
  1382.         $paymentStatus '';
  1383.         foreach ($sales->getPayments() as $payment) {
  1384.             if ($payment->getPaymentStatus() == 2) {
  1385.                 $paymentStatus "TERM_SALE-" $this->translator->trans('payment_status.term_sale');
  1386.                 break;
  1387.             }
  1388.             if ($payment->getPaymentStatus() == 1) {
  1389.                 $paymentStatus "UNPAID-" $this->translator->trans('payment_status.not_paid');
  1390.                 ;
  1391.                 break;
  1392.             }
  1393.             $paymentStatus "PAID-" $this->translator->trans('payment_status.paid');
  1394.             ;
  1395.         }
  1396.         return $paymentStatus;
  1397.     }
  1398.     // PROFORMAYI HTML SAYFASI OLARAK GÖSTERİYOR
  1399.     #[Route('/admin/sales/proforma/{salesId}'name'admin_sales_proforma_show')]
  1400.     public function showInvoice(Request $request$salesId)
  1401.     {
  1402.         $logoPath $this->projectDir '/public/images/decopierrelogo.png';
  1403.         $logoDataUri '';
  1404.         if (file_exists($logoPath)) {
  1405.             $logoBase64 base64_encode(file_get_contents($logoPath));
  1406.             $logoDataUri 'data:' mime_content_type($logoPath) . ';base64,' $logoBase64;
  1407.         }
  1408.         $sales $this->salesServiceImpl->getEntityById($salesId);
  1409.         $html $this->render('/admin/sales/proforma.html.twig', [
  1410.             'proforma' => $sales,
  1411.             'logo_data_uri' => $logoDataUri,
  1412.             'company' => [
  1413.                 'name' => 'DECO PIERRE & NATURE',
  1414.                 'address' => '152 ROUTE DE GRENOBLE 69800 SAINT PRIEST, France',
  1415.                 'siren' => '948485271',
  1416.                 'tva' => 'FR13948485271',
  1417.                 'contact' => 'MATHIAS | 0652901882 | decopierreetnature@gmail.com'
  1418.             ]
  1419.         ]);
  1420.         return $html;
  1421.     }
  1422.     // PROFORMAYI PDF OLARAK İNDİRİYOR
  1423.     #[Route('/admin/sales/proforma/generate-pdf/{salesId}'name'admin_sales_proforma_generate_pdf')]
  1424.     public function generatePdf(Request $request$salesIdPdf $knpSnappyPdf)
  1425.     {
  1426.         $logoPath $this->projectDir '/public/images/decopierrelogo.png';
  1427.         $logoDataUri '';
  1428.         if (file_exists($logoPath)) {
  1429.             $logoBase64 base64_encode(file_get_contents($logoPath));
  1430.             $logoDataUri 'data:' mime_content_type($logoPath) . ';base64,' $logoBase64;
  1431.         }
  1432.         $invoice $this->salesServiceImpl->getEntityById($salesId);
  1433.         // Şirket ve banka bilgilerini içeren bir array oluşturun
  1434.         $companyData = [
  1435.             'name' => 'DECO PIERRE ET NATURE',
  1436.             'address' => '152 ROUTE DE GRENOBLE 69800 SAINT PRIEST, France',
  1437.             'siren' => '948485271',
  1438.             'tva' => 'FR13948485271',
  1439.             'contact' => 'MATHIAS | 0652901882 | decopierreetnature@gmail.com',
  1440.             'bank_details' => [
  1441.                 'titulaire' => 'SAS FAV',
  1442.                 'bank_name' => 'BANQUE POPULAIRE',
  1443.                 'code_banque' => '16807',
  1444.                 'num_compte' => '37402602218',
  1445.                 'iban' => 'FR7616807004133740260221837',
  1446.                 'bic' => 'CCBPFRPPGRE'
  1447.             ]
  1448.         ];
  1449.         // Ana fatura içeriği için Twig render'ı
  1450.         $html $this->renderView('/admin/sales/proforma.html.twig', [
  1451.             'proforma' => $invoice,
  1452.             'logo_data_uri' => $logoDataUri,
  1453.             'company' => $companyData
  1454.         ]);
  1455.         // Footer içeriği için Twig render'ı
  1456.         // NOT: footer.html.twig dosyasına sadece companyData'yı göndermeniz yeterlidir.
  1457.         $footerHtml $this->renderView('/admin/sales/invoice_footer.html.twig', [
  1458.             'company' => $companyData
  1459.         ]);
  1460.         $options = [
  1461.             'footer-html' => $footerHtml,
  1462.             'margin-bottom' => 30,
  1463.             'enable-local-file-access' => true,
  1464.             'encoding' => 'UTF-8',
  1465.         ];
  1466.         return new PdfResponse(
  1467.             $knpSnappyPdf->getOutputFromHtml($html$options),
  1468.             $invoice->getInvoiceNumber() . '.pdf',
  1469.             'inline'
  1470.         );
  1471.     }
  1472.     // PROFORMAYI PDF OLARAK KAYDEDİYOR DOSYA YOLU DÖNÜYOR
  1473.     private function createAndSavePdf(int $invoiceId): ?string
  1474.     {
  1475.         $content $this->getPdfContentFromProforma($invoiceId);
  1476.         return $this->pdfService->convertAndSave($content['html'], $invoiceId'proforma'$content['options']);
  1477.     }
  1478.     // PROFORMAYI PDF OLARAK AÇAR ANCAK YAZDIRMA SEÇENEĞİ AÇILMAZ
  1479.     #[Route('/admin/sales/proforma/send-pdf/{salesId}'name'admin_sales_proforma_send_pdf')]
  1480.     public function sendPdf(Request $requestint $salesId): Response
  1481.     {
  1482.         $filePath $this->createAndSavePdf($salesId);
  1483.         if (!$filePath || !file_exists($filePath)) {
  1484.             throw $this->createNotFoundException('PDF dosyası oluşturulamadı veya bulunamadı.');
  1485.         }
  1486.         $sales $this->salesServiceImpl->getEntityById($salesId);
  1487.         $fileName $sales $sales->getInvoiceNumber() . '.pdf' 'fatura.pdf';
  1488.         $response = new BinaryFileResponse($filePath);
  1489.         $response->setContentDisposition(
  1490.             ResponseHeaderBag::DISPOSITION_INLINE,
  1491.             $fileName
  1492.         );
  1493.         $response->headers->set('Cache-Control''no-cache, no-store, must-revalidate');
  1494.         $response->headers->set('Pragma''no-cache');
  1495.         $response->headers->set('Expires''0');
  1496.         return $response;
  1497.     }
  1498.     // PROFORMAYI PDF OLARAK AÇAR VE YAZDIRMA SEÇENEĞİ ÇIKAR
  1499.     #[Route('/admin/sales/proforma/preview-print/{salesId}'name'admin_sales_proforma_preview_print')]
  1500.     public function previewAndPrint(int $salesId): Response
  1501.     {
  1502.         return $this->render('/admin/sales/proforma_print_preview.html.twig', [
  1503.             'salesId' => $salesId,
  1504.             'pdf_url' => $this->generateUrl('admin_sales_proforma_send_pdf', ['salesId' => $salesId]),
  1505.         ]);
  1506.     }
  1507.     public function findColumn($rank)
  1508.     {
  1509.         $columns = [
  1510.             'id',
  1511.             'invoiceNumber',
  1512.             'salesCompany',
  1513.             'totalPurchasePrice',
  1514.             'seller',
  1515.             'salesDate'
  1516.         ];
  1517.         return $columns[$rank];
  1518.     }
  1519.     #[Route('/admin/excel')]
  1520.     public function excelDetail()
  1521.     {
  1522.         $spreadsheet IOFactory::load($this->getParameter('kernel.project_dir') . '/public/yeni.xlsx'); // Here we are able to read from the excel file
  1523.         $row $spreadsheet->getActiveSheet()->removeRow(1); // I added this to be able to remove the first file line
  1524.         $sheetData $spreadsheet->getActiveSheet()->toArray(nulltruetruetrue); // here, the read data is turned into an array
  1525.         dd($sheetData);
  1526.     }
  1527.     #[Route('/product-cost-detail/{id}'name'product_cost_detail')]
  1528.     public function getProductCost(Request $request$id)
  1529.     {
  1530.         $warehouseId $request->get('warehouse');
  1531.         $product $this->productService->getEntityId($id);
  1532.         $warehouse $this->entityManager->getRepository(Warehouse::class)->findOneBy(['id' => $warehouseId]);
  1533.         /**
  1534.          * @var CalculatorProductsCostsDTO $cost
  1535.          */
  1536.         $cost $this->stockRepository->calculateCost($id$warehouse);
  1537.         $stock $this->stockInfoService->getStockInfoByProductAndWarehouse($product$warehouse);
  1538.         $info = [
  1539.             'cost' => $cost->getAverageCostPrice(),
  1540.             'stock' => $stock->getTotalQuantity(),
  1541.             'measurement' => $product->getMeasurementUnit()?->getName(),
  1542.         ];
  1543.         return $this->json($info);
  1544.     }
  1545.     #[Route('/hesapla/kar')]
  1546.     public function calculateProfitOrDamage(EntityManagerInterface $entityManager)
  1547.     {
  1548.         $salesId 13;
  1549.         $productId 16;
  1550.         $quantity 8;
  1551.         $totalPrice 55.1;
  1552.         $sale $this->salesService->getEntityById($salesId);
  1553.         $product $this->productService->getEntityId($productId);
  1554.         $product->getStockTransferProducts();
  1555.         $warehouse $sale->getWarehouse();
  1556.         $stockRepo $entityManager->getRepository(Stock::class);
  1557.         $lastStock $stockRepo->findOneBy(['product' => $product'warehouse' => $warehouse], ['createdAt' => 'desc']);
  1558.         $stockTransfer $lastStock->getStockTransfer();
  1559.         dd($stockTransfer);
  1560.         dd($product->getStockTransferProducts());
  1561.         //        $transferProduct = $this->stockTransferService->getEntityById($sale-)
  1562.     }
  1563. }