src/Controller/StockController.php line 286

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use App\Entity\Category;
  4. use App\Entity\Discount;
  5. use App\Entity\DiscountProducts;
  6. use App\Entity\Pack;
  7. use App\Entity\PackProducts;
  8. use App\Entity\Product;
  9. use App\Entity\Shop;
  10. use App\Entity\Stock;
  11. use App\Entity\User;
  12. use App\Form\StockFormType;
  13. use App\Repository\ProductRepository;
  14. use App\Repository\ShopRepository;
  15. use App\Repository\StockRepository;
  16. use App\Service\Tools;
  17. use Doctrine\ORM\EntityManagerInterface;
  18. use Mike42\Escpos\PrintConnectors\WindowsPrintConnector;
  19. use Mike42\Escpos\Printer;
  20. use Picqer\Barcode\Barcode;
  21. use Picqer\Barcode\BarcodeGenerator;
  22. use Picqer\Barcode\BarcodeGeneratorHTML;
  23. use Picqer\Barcode\BarcodeGeneratorPNG;
  24. use Saci\Escpos\PrintConnectors\FilePrintConnector;
  25. use Symfony\Component\HttpFoundation\BinaryFileResponse;
  26. use Symfony\Component\HttpFoundation\JsonResponse;
  27. use Symfony\Component\HttpFoundation\Request;
  28. use Symfony\Component\HttpFoundation\Response;
  29. use Symfony\Component\Routing\Annotation\Route;
  30. use function MongoDB\BSON\fromJSON;
  31. class StockController extends AppController
  32. {
  33.     /**
  34.      * @Route("/stocks", name="app_stocks")
  35.      */
  36.     public function index1(EntityManagerInterface $entityManager): Response
  37.     {
  38.         /** @var Category[] $categories */
  39.         $categories $entityManager->getRepository(Category::class)->findBy([],['label' => 'asc']);
  40.         return $this->renderWithParams('stocks/index.html.twig', [
  41.             'catsFilter' => $categories
  42.         ]);
  43.     }
  44.     /**
  45.      * @Route("/details-stock/{productId}/{shopId?}", name="app_stock_details")
  46.      */
  47.     public function stockDetails$productIdEntityManagerInterface $entityManagerProductRepository $productRepositoryShopRepository $shopRepository, ?string $shopId null): Response
  48.     {
  49.         /** @var Product $product */
  50.         $product $productRepository->find($productId);
  51.         /** @var Shop $shop */
  52.         $shop $shopId $shopRepository->find($shopId) : null;
  53.         /** @var Category[] $categories */
  54.         $categories $entityManager->getRepository(Category::class)->findBy([],['label' => 'asc']);
  55. //        if($this->getParameter('app.local') !== null){
  56. //            $local = $this->getParameter('app.local') == 'CTR' ? 'Centre Ville': 'Bensouna';
  57. //        }else{
  58. //
  59. //        }
  60.         $local 'Bensouna';
  61.         return $this->renderWithParams('stocks/details.html.twig', [
  62.             'catsFilter' => $categories,
  63.             'product' => $product,
  64.             'selectedShop' => $shop,
  65.             'local' => $local
  66.         ]);
  67.     }
  68.     /**
  69.      * @Route("/stock-creation/{productId?}", name="create_stock")
  70.      */
  71.     public function createRequest $request,  EntityManagerInterface $em ,?string $productId null): Response
  72.     {
  73.         $stock = new Stock();
  74.         $stock->setCreationDate(new \DateTime());
  75.         $stock->setIsSynch(true);
  76.         $stock->setTarget($this->getParameter('app.target'));
  77.         $stock->setDeleted(false);
  78.         /** @var Product $productSelec */
  79.         $productSelec $productId $em->getRepository(Product::class)->find($productId) : null;
  80.         $stock->setProduct($productSelec);
  81.         $creationForm $this->createForm(StockFormType::class, $stock, []);
  82.         // Gestion de requête du formulaire
  83.         $creationForm->handleRequest($request);
  84.         // Validation du formulaire
  85.         if ($creationForm->isSubmitted() && $creationForm->isValid()) {
  86.             try {
  87.                 $shops $em->getRepository(Shop::class)->findAll();
  88.                 /** @var Product $selectedProduct */
  89.                 $selectedProduct $em->getRepository(Product::class)->find($creationForm->get('selectedProduct')->getViewData());
  90.                 if($selectedProduct != null) {
  91.                     $stock->setProduct($selectedProduct);
  92.                 }
  93.                 foreach ($shops as $key => $shop){
  94.                     $existing true;
  95.                     $lot '';
  96.                     $qteShop $creationForm->get('shop_'.($key+1))->getData();
  97.                     $stockShop = clone $stock;
  98.                     if($productSelec != null && $stockShop->getProduct()->getId() != $productSelec->getId()){
  99.                         $this->addFlash(
  100.                             'danger',
  101.                             "Erreur | Le produit sélectionné ne correspond pas au stock actuel"
  102.                         );
  103.                         return $this->renderWithParams('stocks/create.html.twig', [
  104.                             'form' => $creationForm->createView(),
  105.                             'edition' => false
  106.                         ]);
  107.                     }
  108.                     $stockShop->setInitialQuantity($qteShop);
  109.                     $stockShop->setQuantity($qteShop);
  110.                     $stockShop->setShop($shop);
  111.                     while ($existing){
  112.                         $lot $this->guessLot($stockShop);
  113.                         $stockLot $em->getRepository(Stock::class)->findBy(['lot'=>$lot]);
  114.                         if(empty($stockLot)){
  115.                            $existing false;
  116.                         }
  117.                     }
  118.                     $stockShop->setLot($lot);
  119.                     if($stockShop->getQuantity() > ){
  120.                         $em->persist($stockShop);
  121.                         // 04/05/2024 => application du prix de vente à tous les stocks du même article.
  122.                         $otherStocks $stockShop->getProduct()->getStocks()->toArray();
  123.                         foreach ($otherStocks as $stk){
  124.                             $stk->setUnitAmount($stockShop->getUnitAmount());
  125.                             if($stk->getPackageCount() == $stockShop->getPackageCount()){
  126.                                 $stk->setPackagePrice($stockShop->getPackagePrice());
  127.                             }
  128.                             $stk->setPmpAmount($stockShop->getPmpAmount());
  129. //                            $stk->setPurchasePrice($stock->getPurchasePrice());
  130.                             $stk->setPriceChange(true);
  131.                             $stk->setTarget($this->getParameter('app.target'));
  132.                             $em->persist($stk);
  133.                         }
  134.                     }
  135.                 }
  136.                 $em->flush();
  137.             } catch (\Exception $exception) {
  138.                 $this->addFlash(
  139.                     'danger',
  140.                     "Erreur | " $exception->getMessage()
  141.                 );
  142.                 return $this->renderWithParams('stocks/create.html.twig', [
  143.                     'form' => $creationForm->createView(),
  144.                     'edition' => false,
  145.                     'product' => $productSelec
  146.                 ]);
  147.             }
  148.             $this->addFlash(
  149.                 'success',
  150.                 'Stock enregistré.'
  151.             );
  152.             if($productSelec != null){
  153.                 return $this->redirectToRoute('app_stock_details', ['productId' => $productSelec->getId()]);
  154.             }else{
  155.                 return $this->redirectToRoute('app_stocks');
  156.             }
  157.         }
  158.         return $this->renderWithParams('stocks/create.html.twig', [
  159.             'form' => $creationForm->createView(),
  160.             'edition' => false,
  161.             'product' => $productSelec
  162.         ]);
  163.     }
  164.     /**
  165.      * @Route("/stock-edition/{id}", name="edit_stock")
  166.      */
  167.     public function edit($idRequest $requestEntityManagerInterface $em): Response
  168.     {
  169.         /** @var Stock $stock */
  170.         $stock $em->getRepository(Stock::class)->find($id);
  171.         $creationForm $this->createForm(StockFormType::class, $stock, []);
  172.         // Gestion de requête du formulaire
  173.         $creationForm->handleRequest($request);
  174.         // Validation du formulaire
  175.         if ($creationForm->isSubmitted() && $creationForm->isValid()) {
  176.             try {
  177.                 $otherStocks $stock->getProduct()->getStocks()->toArray();
  178.                 foreach ($otherStocks as $stk){
  179.                     $stk->setUnitAmount($stock->getUnitAmount());
  180.                     if($stk->getPackageCount() == $stock->getPackageCount()){
  181.                         $stk->setPackagePrice($stock->getPackagePrice());
  182.                     }
  183.                     $stk->setPmpAmount($stock->getPmpAmount());
  184.                     // on propage le prix d'achat que si c'est le même lot de l'autre boutique code barres + date de création
  185.                     if($stk->getBarcode() === $stock->getBarcode() and $stk->getCreationDate() == $stock->getCreationDate()){
  186.                         $stk->setPurchasePrice($stock->getPurchasePrice());
  187.                     }
  188.                     $stk->setPriceChange(true);
  189.                     $stk->setTarget($this->getParameter('app.target'));
  190.                     $em->persist($stk);
  191.                 }
  192.                 $stock->setIsSynch(true);
  193.                 $stock->setTarget($this->getParameter('app.target'));
  194.                 $em->persist($stock);
  195.                 $em->flush();
  196.             } catch (\Exception $exception) {
  197.                 $this->addFlash(
  198.                     'danger',
  199.                     "Erreur | " $exception->getMessage()
  200.                 );
  201.                 return $this->redirectToRoute('app_stocks');
  202.             }
  203.             $this->addFlash(
  204.                 'success',
  205.                 'Stock enregistré.'
  206.             );
  207.             return $this->redirectToRoute('app_stocks');
  208.         }
  209.         return $this->renderWithParams('stocks/create.html.twig', [
  210.             'form' => $creationForm->createView(),
  211.             'edition' => true,
  212.             'stockId' => $stock->getId()
  213.         ]);
  214.     }
  215.     /**
  216.      * @Route("/stock-remove/{id}", name="remove_stock")
  217.      */
  218.     public function remove($idRequest $requestEntityManagerInterface $em): Response
  219.     {
  220.         /** @var Stock $stock */
  221.         $stock $em->getRepository(Stock::class)->find($id);
  222.         try {
  223.             $stock->setIsSynch(true);
  224.             $stock->setTarget($this->getParameter('app.target'));
  225.             $stock->setDeleted(true);
  226.             $em->persist($stock);
  227.             $em->flush();
  228.         } catch (\Exception $exception) {
  229.             $this->addFlash(
  230.                 'danger',
  231.                 "Erreur | " $exception->getMessage()
  232.             );
  233.             return $this->redirectToRoute('app_stocks');
  234.         }
  235.         $this->addFlash(
  236.             'success',
  237.             'Stock supprimé.'
  238.         );
  239.         return $this->redirectToRoute('app_stocks');
  240.     }
  241.     /**
  242.      * @Route("/stock-get-product/{code}", name="get_stock_product")
  243.      */
  244.     public function getStockProduct($codeRequest $requestEntityManagerInterface $em): JsonResponse
  245.     {
  246.         $product=[];
  247.         if($this->getUser() != null){
  248.             /** @var User $user */
  249.             $user $em->getRepository(User::class)->findOneBy(['username' => $this->getUser()->getUserIdentifier()]);
  250.         }else{
  251.             /** @var User $user */
  252.             $user $em->getRepository(User::class)->find(5);
  253.         }
  254.         $shop $em->getRepository(Shop::class)->find($user->getCurrentshop());
  255.         /** @var Stock $stock */
  256.         $stock $em->getRepository(Stock::class)->findOneBy(['packageBarcode' => $code'shop'=> $shop->getId(), 'deleted' => false], ['id' => 'desc']);
  257.         if($stock != null){
  258.             $rate 0;
  259.             $min 0;
  260.             $stock->getProduct()->getDiscountedProducts();
  261.             /** @var DiscountProducts $discount */
  262.             foreach ($stock->getProduct()->getDiscountedProducts()->toArray() as $discount){
  263.                 if($discount->getDiscount()->isStatus()){
  264.                     $rate $discount->getDiscount()->getRate();
  265.                     $min $discount->getDiscount()->getMinimum();
  266.                 }
  267.             }
  268.             $product = [
  269.                 'id' => $stock->getProduct()->getId(),
  270.                 'label' => $stock->getPackageName() != null $stock->getPackageName() : $stock->getProduct()->getLabel(),
  271.                 'price' => $stock->getPackagePrice() != null $stock->getPackagePrice() : $stock->getUnitAmount(),
  272.                 'rate' => $rate,
  273.                 'min' => $min,
  274.                 'code' => $stock->getBarcode(),
  275.                 'weight' => $stock->getProduct()->getUnit() == 'WEIGHT',
  276.                 'sold' => $rate != 0,
  277.                 'pack' => false,
  278.                 'package' => true,
  279.                 'packageCount' => $stock->getPackageCount()
  280.             ];
  281.         }
  282.         /** @var Stock $stock */
  283.         $stock $em->getRepository(Stock::class)->findOneBy(['barcode' => $code'shop'=> $shop->getId(), 'deleted' => false], ['id' => 'desc']);
  284.         if($stock != null /*and $stock->getQuantity() > 0*/ ){
  285.             $rate 0;
  286.             $min 0;
  287.             $stock->getProduct()->getDiscountedProducts();
  288.             /** @var DiscountProducts $discount */
  289.             foreach ($stock->getProduct()->getDiscountedProducts()->toArray() as $discount){
  290.                 if($discount->getDiscount()->isStatus()){
  291.                     $rate $discount->getDiscount()->getRate();
  292.                     $min $discount->getDiscount()->getMinimum();
  293.                 }
  294.             }
  295.             $product = [
  296.                 'id' => $stock->getProduct()->getId(),
  297.                 'label' => $stock->getProduct()->getLabel(),
  298.                 'price' => $stock->getUnitAmount(),
  299.                 'rate' => $rate,
  300.                 'min' => $min,
  301.                 'code' => $stock->getBarcode(),
  302.                 'weight' => $stock->getProduct()->getUnit() == 'WEIGHT',
  303.                 'sold' => $rate != 0,
  304.                 'pack' => false,
  305.                 'package' => false,
  306.                 'packageCount' => 0
  307.             ];
  308.         }
  309.         /** @var Pack $stock */
  310.         $stock $em->getRepository(Pack::class)->findOneBy(['barcode' => $code], ['id' => 'desc']);
  311.         if($stock != null){
  312.             $product = [
  313.                 'id' => $stock->getId(),
  314.                 'label' => $stock->getLabel(),
  315.                 'price' => $stock->getPrice(),
  316.                 'rate' => 0,
  317.                 'min' => 0,
  318.                 'code' => $stock->getBarcode(),
  319.                 'weight' => false,
  320.                 'sold' => false,
  321.                 'pack' => true,
  322.                 'package' => false,
  323.                 'packageCount' => 0
  324.             ];
  325.         }
  326.         $response = new JsonResponse(
  327.             json_encode($product),
  328.             200
  329.         );
  330.         return  $response;
  331.     }
  332.     /**
  333.      * @Route("/stock-get-by-product-id/{id}", name="get_stock_product_byId")
  334.      */
  335.     public function getStockByProductId($idRequest $requestEntityManagerInterface $em): JsonResponse
  336.     {
  337.         $products=[];
  338.         if($this->getUser() != null){
  339.             /** @var User $user */
  340.             $user $em->getRepository(User::class)->findOneBy(['username' => $this->getUser()->getUserIdentifier()]);
  341.         }else{
  342.             /** @var User $user */
  343.             $user $em->getRepository(User::class)->find(5);
  344.         }
  345.         $shop $em->getRepository(Shop::class)->find($user->getCurrentshop());
  346.         /** @var Product $product */
  347.         $product $em->getRepository(Product::class)->find($id);
  348. //        dd($product->getId(), $shop->getId(),$em->getRepository(Stock::class)->findCurrentStock($product->getId(), $shop->getId()));
  349.         /** @var Stock $stock */
  350.         $stock $em->getRepository(Stock::class)->findCurrentStock($product->getId(), $shop->getId())[0];
  351.         if($stock != null){
  352.             $rate 0;
  353.             $min 0;
  354.             $stock->getProduct()->getDiscountedProducts();
  355.             /** @var DiscountProducts $discount */
  356.             foreach ($stock->getProduct()->getDiscountedProducts()->toArray() as $discount){
  357.                 if($discount->getDiscount()->isStatus()){
  358.                     $rate $discount->getDiscount()->getRate();
  359.                     $min $discount->getDiscount()->getMinimum();
  360.                 }
  361.             }
  362.             $products = [
  363.                 'id' => $stock->getProduct()->getId(),
  364.                 'label' => $stock->getProduct()->getLabel(),
  365.                 'price' => $stock->getUnitAmount(),
  366.                 'rate' => $rate,
  367.                 'min' => $min,
  368.                 'code' => $stock->getBarcode(),
  369.                 'weight' => $stock->getProduct()->getUnit() == 'WEIGHT',
  370.                 'sold' => $rate != 0,
  371.                 'pack' => false,
  372.                 'package' => false,
  373.                 'packageCount' => 0
  374.             ];
  375.         }
  376.         $response = new JsonResponse(
  377.             json_encode($products),
  378.             200
  379.         );
  380.         return  $response;
  381.     }
  382.     /**
  383.      * @Route("/stock-get-by-product-id-checkout/{id}", name="get_stock_product_byId_Checkout")
  384.      */
  385.     public function getStockByProductIdForCheckout($idRequest $requestEntityManagerInterface $em): JsonResponse
  386.     {
  387.         $products=[];
  388.         if($this->getUser() != null){
  389.             /** @var User $user */
  390.             $user $em->getRepository(User::class)->findOneBy(['username' => $this->getUser()->getUserIdentifier()]);
  391.         }else{
  392.             /** @var User $user */
  393.             $user $em->getRepository(User::class)->find(5);
  394.         }
  395.         $shop $em->getRepository(Shop::class)->find($user->getCurrentshop());
  396.         /** @var Product $product */
  397.         $product $em->getRepository(Product::class)->find($id);
  398. //        dd($product->getId(), $shop->getId(),$em->getRepository(Stock::class)->findCurrentStock($product->getId(), $shop->getId()));
  399.         /** @var Stock $stock */
  400.         $currentStock $em->getRepository(Stock::class)->findCurrentStock($product->getId(), $shop->getId());
  401.         if(empty($currentStock)){
  402.             $stock $em->getRepository(Stock::class)->findLastStock($product->getId(), $shop->getId())[0];
  403.         }else{
  404.             $stock $currentStock[0];
  405.         }
  406.         if($stock != null){
  407.         $rate 0;
  408.         $min 0;
  409.         $stock->getProduct()->getDiscountedProducts();
  410.         /** @var DiscountProducts $discount */
  411.         foreach ($stock->getProduct()->getDiscountedProducts()->toArray() as $discount){
  412.             if($discount->getDiscount()->isStatus()){
  413.                 $rate $discount->getDiscount()->getRate();
  414.                 $min $discount->getDiscount()->getMinimum();
  415.             }
  416.         }
  417.         $products = [
  418.             'id' => $stock->getProduct()->getId(),
  419.             'label' => $stock->getProduct()->getLabel(),
  420.             'price' => $stock->getUnitAmount(),
  421.             'rate' => $rate,
  422.             'min' => $min,
  423.             'code' => $stock->getBarcode(),
  424.             'weight' => $stock->getProduct()->getUnit() == 'WEIGHT',
  425.             'sold' => $rate != 0,
  426.             'pack' => false,
  427.             'package' => false,
  428.             'packageCount' => 0
  429.         ];
  430.         }
  431.         $response = new JsonResponse(
  432.             json_encode($products),
  433.             200
  434.         );
  435.         return  $response;
  436.     }
  437.     /**
  438.      * @Route("/stock-generate-barcode/{code}", name="stock_generate_barcode")
  439.      */
  440.     public function generateBarCode(string $code): Response
  441.     {
  442.         $generator = new BarcodeGeneratorPNG();
  443. //        $image = 'barcode.png';
  444. //        file_put_contents('barcode.png', $generator->getBarcode($code, BarcodeGenerator::TYPE_CODE_128, 3, 100));
  445. //        return new BinaryFileResponse($image);
  446.         $data $generator->getBarcode($codeBarcodeGenerator::TYPE_CODE_1282100);
  447.         return new JsonResponse(
  448.             base64_encode($data),
  449.             200
  450.         );
  451.     }
  452.     /**
  453.      * @Route("/stock-print-barcode/{code}", name="stock_print_barcode")
  454.      */
  455.     public function printCodeBar(string $code):Response
  456.     {
  457.         try {
  458.             // Enter the share name for your USB printer here
  459.             // $connector = null;
  460. //            $connector = new WindowsPrintConnector("Generic  Text Only");
  461.             $connector = new WindowsPrintConnector("PRINTER");
  462.             /* Print a "Hello world" receipt" */
  463.             $printer = new Printer($connector);
  464.             $printer -> text("Hello World!\n");
  465.             $printer -> cut();
  466.             /* Close printer */
  467.             $printer -> close();
  468.         } catch (\Exception $e) {
  469.             echo "Couldn't print to this printer: " $e -> getMessage() . "\n";
  470.         }
  471.         return new JsonResponse();
  472.     }
  473.     /**
  474.      * @Route("/get-pmp/{id}/{stockId}", name="stock_get_pmp", defaults={"stockId"=null})
  475.      */
  476.     public function getPMP(
  477.         string $id,
  478.         ?string $stockId,
  479.         ProductRepository $productRepository,
  480.         StockRepository $stockRepository
  481.     ): JsonResponse {
  482.         $product $productRepository->find($id);
  483.         $qb $stockRepository->createQueryBuilder('s')
  484.             ->where('s.product = :product')
  485.             ->andWhere('s.deleted = :deleted')
  486.             ->andWhere('s.quantity > 0')
  487.             ->setParameter('product'$product)
  488.             ->setParameter('deleted'false)
  489.             ->orderBy('s.creation_date''ASC');
  490. //        if ($stockId !== null) {
  491. //            $qb->andWhere('s.id != :stockId')
  492. //                ->setParameter('stockId', $stockId);
  493. //        }
  494.         $stocks $qb->getQuery()->getResult();
  495.         $qteAvailable 0;
  496. //        dump($stocks);
  497.         /** @var Stock $stock */
  498.         foreach ($stocks as $stock) {
  499.             if ($stock->getQuantity() > 0) {
  500.                 $qteAvailable += $stock->getQuantity();
  501.             }
  502.         }
  503.         $lastPmp end($stocks);
  504.         $response = [
  505.             'lastPmp' => $lastPmp $lastPmp->getPmpAmount() : 0,
  506.             'qteAvailable' => $qteAvailable
  507.         ];
  508.         return new JsonResponse($response200);
  509.     }
  510.     private function guessLot(Stock $stock):int
  511.     {
  512.         $uniqueCode rand(1000000,9999999);
  513.         return '700'.str_shuffle($uniqueCode);
  514.     }
  515.     /**
  516.      * @Route("/update-remaining/{lotId}", name="app_stock_update_remaining")
  517.      */
  518.     public function updateRemaining(
  519.         string $lotId,
  520.         Request $request,
  521.         StockRepository $lotRepository,
  522.         EntityManagerInterface $em
  523.     ): JsonResponse {
  524.         // 1) Parse JSON
  525.         $payload json_decode($request->getContent(), true);
  526.         if (!is_array($payload) || !array_key_exists('remainingQty'$payload)) {
  527.             return $this->json(['success' => false'error' => 'Paramètre "remainingQty" manquant.'], 400);
  528.         }
  529.         if (!is_numeric($payload['remainingQty'])) {
  530.             return $this->json(['success' => false'error' => '"remainingQty" doit être un nombre.'], 400);
  531.         }
  532.         $remainingQty = (int) $payload['remainingQty'];
  533.         if ($remainingQty 0) {
  534.             return $this->json(['success' => false'error' => '"remainingQty" doit être ≥ 0.'], 422);
  535.         }
  536.         // 2) Retrouver le lot
  537.         // Si ton identifiant public est le N° de lot (ex: 8001751) stocké dans une colonne lotNumber :
  538.         $lot $lotRepository->find($lotId);
  539.         // Sinon, si {lotId} est l'ID auto-incrémenté, dé-commente :
  540.         // $lot = $lotRepository->find($lotId);
  541.         if (!$lot) {
  542.             return $this->json(['success' => false'error' => 'Lot introuvable.'], 404);
  543.         }
  544.         // 3) Règles métier optionnelles (décommente/ajuste selon ton modèle)
  545.         // if ($remainingQty > $lot->getQtyInitial()) {
  546.         //     return $this->json(['success' => false, 'error' => 'La quantité restante ne peut pas dépasser la quantité initiale.'], 422);
  547.         // }
  548.         // 4) Mise à jour + flush
  549.         $lot->setQuantity($remainingQty);
  550.         // Exemple : maj d’un flag "finished"
  551.         // $lot->setFinished($remainingQty === 0);
  552.         $em->flush();
  553.         // 5) Réponse
  554.         return $this->json([
  555.             'success' => true,
  556.             'lotId' => $lotId,
  557.             'remainingQty' => $remainingQty,
  558.             // 'finished' => $lot->isFinished(),
  559.         ], 200);
  560.     }
  561. }