<?php
namespace App\Controller;
use App\Entity\Category;
use App\Entity\Discount;
use App\Entity\DiscountProducts;
use App\Entity\Pack;
use App\Entity\PackProducts;
use App\Entity\Product;
use App\Entity\Shop;
use App\Entity\Stock;
use App\Entity\User;
use App\Form\StockFormType;
use App\Repository\ProductRepository;
use App\Repository\ShopRepository;
use App\Repository\StockRepository;
use App\Service\CrmLogger;
use App\Service\Tools;
use Doctrine\ORM\EntityManagerInterface;
use Mike42\Escpos\PrintConnectors\WindowsPrintConnector;
use Mike42\Escpos\Printer;
use Picqer\Barcode\Barcode;
use Picqer\Barcode\BarcodeGenerator;
use Picqer\Barcode\BarcodeGeneratorHTML;
use Picqer\Barcode\BarcodeGeneratorPNG;
use Saci\Escpos\PrintConnectors\FilePrintConnector;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use function MongoDB\BSON\fromJSON;
class StockController extends AppController
{
/**
* @Route("/stocks", name="app_stocks")
*/
public function index1(EntityManagerInterface $entityManager): Response
{
/** @var Category[] $categories */
$categories = $entityManager->getRepository(Category::class)->findBy([],['label' => 'asc']);
return $this->renderWithParams('stocks/index.html.twig', [
'catsFilter' => $categories
]);
}
/**
* @Route("/details-stock/{productId}/{shopId?}", name="app_stock_details")
*/
public function stockDetails( $productId, EntityManagerInterface $entityManager, ProductRepository $productRepository, ShopRepository $shopRepository, ?string $shopId = null): Response
{
/** @var Product $product */
$product = $productRepository->find($productId);
/** @var Shop $shop */
$shop = $shopId ? $shopRepository->find($shopId) : null;
/** @var Category[] $categories */
$categories = $entityManager->getRepository(Category::class)->findBy([],['label' => 'asc']);
if($this->getParameter('app.local') !== null){
$local = $this->getParameter('app.local') == 'CTR' ? 'Centre Ville': 'Bensouna';
}else{
$local = 'Bensouna';
}
return $this->renderWithParams('stocks/details.html.twig', [
'catsFilter' => $categories,
'product' => $product,
'selectedShop' => $shop,
'local' => $local
]);
}
/**
* @Route("/stock-creation/{productId?}", name="create_stock")
*/
public function create( Request $request, EntityManagerInterface $em ,?string $productId = null, CrmLogger $crmLogger): Response
{
$stock = new Stock();
$stock->setCreationDate(new \DateTime());
$stock->setIsSynch(true);
$stock->setTarget($this->getParameter('app.target'));
$stock->setDeleted(false);
/** @var Product $productSelec */
$productSelec = $productId ? $em->getRepository(Product::class)->find($productId) : null;
$stock->setProduct($productSelec);
$creationForm = $this->createForm(StockFormType::class, $stock, []);
// Gestion de requête du formulaire
$creationForm->handleRequest($request);
// Validation du formulaire
if ($creationForm->isSubmitted() && $creationForm->isValid()) {
try {
$shops = $em->getRepository(Shop::class)->findAll();
/** @var Product $selectedProduct */
$selectedProduct = $em->getRepository(Product::class)->find($creationForm->get('selectedProduct')->getViewData());
if($selectedProduct != null) {
$stock->setProduct($selectedProduct);
}
foreach ($shops as $key => $shop){
$existing = true;
$lot = '';
$qteShop = $creationForm->get('shop_'.($key+1))->getData();
$stockShop = clone $stock;
if($productSelec != null && $stockShop->getProduct()->getId() != $productSelec->getId()){
$this->addFlash(
'danger',
"Erreur | Le produit sélectionné ne correspond pas au stock actuel"
);
$crmLogger->log(
'ERREUR',
'STOCK_CREATION_MISMATCH_PRODUCT',
'Le produit sélectionné ne correspond pas au stock actuel ID Produit Formulaire : '.$stockShop->getProduct()->getId().' | ID Produit URL : '.$productSelec->getId(),
$shop->getLabel(),
$this->getUser() ? $this->getUser()->getUserIdentifier() : 'Anonyme'
);
return $this->renderWithParams('stocks/create.html.twig', [
'form' => $creationForm->createView(),
'edition' => false
]);
}
$stockShop->setInitialQuantity($qteShop);
$stockShop->setQuantity($qteShop);
$stockShop->setShop($shop);
while ($existing){
$lot = $this->guessLot($stockShop);
$stockLot = $em->getRepository(Stock::class)->findBy(['lot'=>$lot]);
if(empty($stockLot)){
$existing = false;
}
}
$stockShop->setLot($lot);
if($stockShop->getQuantity() > 0 ){
$em->persist($stockShop);
$crmLogger->log(
'CREATION',
'STOCK_CREATION_SUCCESS',
sprintf('Produit : %d %s barcode : %s Qte %d prix %s pmp %s', $stockShop->getProduct()->getId(), $stockShop->getProduct()->getLabel(), $stockShop->getBarcode(), $stockShop->getQuantity(), $stockShop->getUnitAmount(), $stockShop->getPmpAmount()),
$stock->getShop() ? $stock->getShop()->getLabel() : 'N/A',
$this->getUser() ? $this->getUser()->getUserIdentifier() : 'Anonyme'
);
// 04/05/2024 => application du prix de vente à tous les stocks du même article.
$otherStocks = $stockShop->getProduct()->getStocks()->toArray();
$stocksPropaged = '';
foreach ($otherStocks as $stk){
$stk->setUnitAmount($stockShop->getUnitAmount());
if($stk->getPackageCount() == $stockShop->getPackageCount()){
$stk->setPackagePrice($stockShop->getPackagePrice());
}
$stk->setPmpAmount($stockShop->getPmpAmount());
// $stk->setPurchasePrice($stock->getPurchasePrice());
$stk->setPriceChange(true);
$stk->setTarget($this->getParameter('app.target'));
$em->persist($stk);
$stocksPropaged .= $stk->getId() .' | ';
}
$crmLogger->log(
'PROPAGATION',
'STOCK_PROPAGATION_CREATION_SUCCESS',
sprintf('List des stocks propagés : %s ', $stocksPropaged),
$stock->getShop() ? $stock->getShop()->getLabel() : 'N/A',
$this->getUser() ? $this->getUser()->getUserIdentifier() : 'Anonyme'
);
}
}
$em->flush();
} catch (\Exception $exception) {
$this->addFlash(
'danger',
"Erreur | " . $exception->getMessage()
);
$crmLogger->log(
'ERREUR',
'STOCK_CREATION_EXCEPTION',
'Exception lors de la création du stock : '.$exception->getMessage(),
$stock->getShop() ? $stock->getShop()->getLabel() : 'N/A',
$this->getUser() ? $this->getUser()->getUserIdentifier() : 'Anonyme'
);
return $this->renderWithParams('stocks/create.html.twig', [
'form' => $creationForm->createView(),
'edition' => false,
'product' => $productSelec
]);
}
$this->addFlash(
'success',
'Stock enregistré.'
);
if($productSelec != null){
return $this->redirectToRoute('app_stock_details', ['productId' => $productSelec->getId()]);
}else{
return $this->redirectToRoute('app_stocks');
}
}
return $this->renderWithParams('stocks/create.html.twig', [
'form' => $creationForm->createView(),
'edition' => false,
'product' => $productSelec
]);
}
/**
* @Route("/stock-edition/{id}", name="edit_stock")
*/
public function edit($id, Request $request, EntityManagerInterface $em, CrmLogger $crmLogger): Response
{
/** @var Stock $stock */
$stock = $em->getRepository(Stock::class)->find($id);
$creationForm = $this->createForm(StockFormType::class, $stock, []);
// Gestion de requête du formulaire
$creationForm->handleRequest($request);
// Validation du formulaire
if ($creationForm->isSubmitted() && $creationForm->isValid()) {
try {
$otherStocks = $stock->getProduct()->getStocks()->toArray();
$stocksPropaged = '';
foreach ($otherStocks as $stk){
$stk->setUnitAmount($stock->getUnitAmount());
if($stk->getPackageCount() == $stock->getPackageCount()){
$stk->setPackagePrice($stock->getPackagePrice());
}
$stk->setPmpAmount($stock->getPmpAmount());
// on propage le prix d'achat que si c'est le même lot de l'autre boutique code barres + date de création
if($stk->getBarcode() === $stock->getBarcode() and $stk->getCreationDate() == $stock->getCreationDate()){
$stk->setPurchasePrice($stock->getPurchasePrice());
}
$stk->setPriceChange(true);
$stk->setTarget($this->getParameter('app.target'));
$em->persist($stk);
$stocksPropaged .= $stk->getId() .' | ';
}
$crmLogger->log(
'PROPAGATION',
'STOCK_PROPAGATION_MODIF_SUCCESS',
sprintf('List des stocks propagés : %s ', $stocksPropaged),
$stock->getShop() ? $stock->getShop()->getLabel() : 'N/A',
$this->getUser() ? $this->getUser()->getUserIdentifier() : 'Anonyme'
);
$stock->setIsSynch(true);
$stock->setTarget($this->getParameter('app.target'));
$em->persist($stock);
$em->flush();
$crmLogger->log(
'MODIFICATION',
'STOCK_MODIF_SUCCESS',
sprintf('Modification du stock ID : %d | Produit : %d %s barcode : %s Qte %d prix %s pmp %s', $stock->getId(), $stock->getProduct()->getId(), $stock->getProduct()->getLabel(), $stock->getBarcode(), $stock->getQuantity(), $stock->getUnitAmount(), $stock->getPmpAmount()),
$stock->getShop() ? $stock->getShop()->getLabel() : 'N/A',
$this->getUser() ? $this->getUser()->getUserIdentifier() : 'Anonyme'
);
} catch (\Exception $exception) {
$this->addFlash(
'danger',
"Erreur | " . $exception->getMessage()
);
return $this->redirectToRoute('app_stocks');
}
$this->addFlash(
'success',
'Stock enregistré.'
);
return $this->redirectToRoute('app_stocks');
}
return $this->renderWithParams('stocks/create.html.twig', [
'form' => $creationForm->createView(),
'edition' => true,
'stockId' => $stock->getId()
]);
}
/**
* @Route("/stock-remove/{id}", name="remove_stock")
*/
public function remove($id, Request $request, EntityManagerInterface $em, CrmLogger $crmLogger): Response
{
/** @var Stock $stock */
$stock = $em->getRepository(Stock::class)->find($id);
try {
$stock->setIsSynch(true);
$stock->setTarget($this->getParameter('app.target'));
$stock->setDeleted(true);
$em->persist($stock);
$em->flush();
$crmLogger->log(
'SUPPRESSION',
'STOCK_DELETION_SUCCESS',
sprintf('Suppression du stock ID : %d | Produit : %d %s barcode : %s Qte %d prix %s pmp %s', $stock->getId(), $stock->getProduct()->getId(), $stock->getProduct()->getLabel(), $stock->getBarcode(), $stock->getQuantity(), $stock->getUnitAmount(), $stock->getPmpAmount()),
$stock->getShop() ? $stock->getShop()->getLabel() : 'N/A',
$this->getUser() ? $this->getUser()->getUserIdentifier() : 'Anonyme'
);
} catch (\Exception $exception) {
$this->addFlash(
'danger',
"Erreur | " . $exception->getMessage()
);
return $this->redirectToRoute('app_stocks');
}
$this->addFlash(
'success',
'Stock supprimé.'
);
return $this->redirectToRoute('app_stocks');
}
/**
* @Route("/stock-get-product/{code}", name="get_stock_product")
*/
public function getStockProduct($code, Request $request, EntityManagerInterface $em): JsonResponse
{
$product=[];
if($this->getUser() != null){
/** @var User $user */
$user = $em->getRepository(User::class)->findOneBy(['username' => $this->getUser()->getUserIdentifier()]);
}else{
/** @var User $user */
$user = $em->getRepository(User::class)->find(5);
}
$shop = $em->getRepository(Shop::class)->find($user->getCurrentshop());
/** @var Stock $stock */
$stock = $em->getRepository(Stock::class)->findOneBy(['packageBarcode' => $code, 'shop'=> $shop->getId(), 'deleted' => false], ['id' => 'desc']);
if($stock != null){
$rate = 0;
$min = 0;
$stock->getProduct()->getDiscountedProducts();
/** @var DiscountProducts $discount */
foreach ($stock->getProduct()->getDiscountedProducts()->toArray() as $discount){
if($discount->getDiscount()->isStatus()){
$rate = $discount->getDiscount()->getRate();
$min = $discount->getDiscount()->getMinimum();
}
}
$product = [
'id' => $stock->getProduct()->getId(),
'label' => $stock->getPackageName() != null ? $stock->getPackageName() : $stock->getProduct()->getLabel(),
'price' => $stock->getPackagePrice() != null ? $stock->getPackagePrice() : $stock->getUnitAmount(),
'rate' => $rate,
'min' => $min,
'code' => $stock->getBarcode(),
'weight' => $stock->getProduct()->getUnit() == 'WEIGHT',
'sold' => $rate != 0,
'pack' => false,
'package' => true,
'packageCount' => $stock->getPackageCount()
];
}
/** @var Stock $stock */
$stock = $em->getRepository(Stock::class)->findOneBy(['barcode' => $code, 'shop'=> $shop->getId(), 'deleted' => false], ['id' => 'desc']);
if($stock != null /*and $stock->getQuantity() > 0*/ ){
$rate = 0;
$min = 0;
$stock->getProduct()->getDiscountedProducts();
/** @var DiscountProducts $discount */
foreach ($stock->getProduct()->getDiscountedProducts()->toArray() as $discount){
if($discount->getDiscount()->isStatus()){
$rate = $discount->getDiscount()->getRate();
$min = $discount->getDiscount()->getMinimum();
}
}
$product = [
'id' => $stock->getProduct()->getId(),
'label' => $stock->getProduct()->getLabel(),
'price' => $stock->getUnitAmount(),
'rate' => $rate,
'min' => $min,
'code' => $stock->getBarcode(),
'weight' => $stock->getProduct()->getUnit() == 'WEIGHT',
'sold' => $rate != 0,
'pack' => false,
'package' => false,
'packageCount' => 0
];
}
/** @var Pack $stock */
$stock = $em->getRepository(Pack::class)->findOneBy(['barcode' => $code], ['id' => 'desc']);
if($stock != null){
$product = [
'id' => $stock->getId(),
'label' => $stock->getLabel(),
'price' => $stock->getPrice(),
'rate' => 0,
'min' => 0,
'code' => $stock->getBarcode(),
'weight' => false,
'sold' => false,
'pack' => true,
'package' => false,
'packageCount' => 0
];
}
$response = new JsonResponse(
json_encode($product),
200
);
return $response;
}
/**
* @Route("/stock-get-by-product-id/{id}", name="get_stock_product_byId")
*/
public function getStockByProductId($id, Request $request, EntityManagerInterface $em): JsonResponse
{
$products=[];
if($this->getUser() != null){
/** @var User $user */
$user = $em->getRepository(User::class)->findOneBy(['username' => $this->getUser()->getUserIdentifier()]);
}else{
/** @var User $user */
$user = $em->getRepository(User::class)->find(5);
}
$shop = $em->getRepository(Shop::class)->find($user->getCurrentshop());
/** @var Product $product */
$product = $em->getRepository(Product::class)->find($id);
// dd($product->getId(), $shop->getId(),$em->getRepository(Stock::class)->findCurrentStock($product->getId(), $shop->getId()));
/** @var Stock $stock */
$stock = $em->getRepository(Stock::class)->findCurrentStock($product->getId(), $shop->getId())[0];
if($stock != null){
$rate = 0;
$min = 0;
$stock->getProduct()->getDiscountedProducts();
/** @var DiscountProducts $discount */
foreach ($stock->getProduct()->getDiscountedProducts()->toArray() as $discount){
if($discount->getDiscount()->isStatus()){
$rate = $discount->getDiscount()->getRate();
$min = $discount->getDiscount()->getMinimum();
}
}
$products = [
'id' => $stock->getProduct()->getId(),
'label' => $stock->getProduct()->getLabel(),
'price' => $stock->getUnitAmount(),
'rate' => $rate,
'min' => $min,
'code' => $stock->getBarcode(),
'weight' => $stock->getProduct()->getUnit() == 'WEIGHT',
'sold' => $rate != 0,
'pack' => false,
'package' => false,
'packageCount' => 0
];
}
$response = new JsonResponse(
json_encode($products),
200
);
return $response;
}
/**
* @Route("/stock-get-by-product-id-checkout/{id}", name="get_stock_product_byId_Checkout")
*/
public function getStockByProductIdForCheckout($id, Request $request, EntityManagerInterface $em): JsonResponse
{
$products=[];
if($this->getUser() != null){
/** @var User $user */
$user = $em->getRepository(User::class)->findOneBy(['username' => $this->getUser()->getUserIdentifier()]);
}else{
/** @var User $user */
$user = $em->getRepository(User::class)->find(5);
}
$shop = $em->getRepository(Shop::class)->find($user->getCurrentshop());
/** @var Product $product */
$product = $em->getRepository(Product::class)->find($id);
// dd($product->getId(), $shop->getId(),$em->getRepository(Stock::class)->findCurrentStock($product->getId(), $shop->getId()));
/** @var Stock $stock */
$currentStock = $em->getRepository(Stock::class)->findCurrentStock($product->getId(), $shop->getId());
if(empty($currentStock)){
$stock = $em->getRepository(Stock::class)->findLastStock($product->getId(), $shop->getId())[0];
}else{
$stock = $currentStock[0];
}
if($stock != null){
$rate = 0;
$min = 0;
$stock->getProduct()->getDiscountedProducts();
/** @var DiscountProducts $discount */
foreach ($stock->getProduct()->getDiscountedProducts()->toArray() as $discount){
if($discount->getDiscount()->isStatus()){
$rate = $discount->getDiscount()->getRate();
$min = $discount->getDiscount()->getMinimum();
}
}
$products = [
'id' => $stock->getProduct()->getId(),
'label' => $stock->getProduct()->getLabel(),
'price' => $stock->getUnitAmount(),
'rate' => $rate,
'min' => $min,
'code' => $stock->getBarcode(),
'weight' => $stock->getProduct()->getUnit() == 'WEIGHT',
'sold' => $rate != 0,
'pack' => false,
'package' => false,
'packageCount' => 0
];
}
$response = new JsonResponse(
json_encode($products),
200
);
return $response;
}
/**
* @Route("/stock-generate-barcode/{code}", name="stock_generate_barcode")
*/
public function generateBarCode(string $code): Response
{
$generator = new BarcodeGeneratorPNG();
// $image = 'barcode.png';
// file_put_contents('barcode.png', $generator->getBarcode($code, BarcodeGenerator::TYPE_CODE_128, 3, 100));
// return new BinaryFileResponse($image);
$data = $generator->getBarcode($code, BarcodeGenerator::TYPE_CODE_128, 2, 100);
return new JsonResponse(
base64_encode($data),
200
);
}
/**
* @Route("/stock-print-barcode/{code}", name="stock_print_barcode")
*/
public function printCodeBar(string $code):Response
{
try {
// Enter the share name for your USB printer here
// $connector = null;
// $connector = new WindowsPrintConnector("Generic Text Only");
$connector = new WindowsPrintConnector("PRINTER");
/* Print a "Hello world" receipt" */
$printer = new Printer($connector);
$printer -> text("Hello World!\n");
$printer -> cut();
/* Close printer */
$printer -> close();
} catch (\Exception $e) {
echo "Couldn't print to this printer: " . $e -> getMessage() . "\n";
}
return new JsonResponse();
}
/**
* @Route("/get-pmp/{id}/{stockId}", name="stock_get_pmp", defaults={"stockId"=null})
*/
public function getPMP(
string $id,
?string $stockId,
ProductRepository $productRepository,
StockRepository $stockRepository
): JsonResponse {
$product = $productRepository->find($id);
$qb = $stockRepository->createQueryBuilder('s')
->where('s.product = :product')
->andWhere('s.deleted = :deleted')
->andWhere('s.quantity > 0')
->setParameter('product', $product)
->setParameter('deleted', false)
->orderBy('s.creation_date', 'ASC');
// if ($stockId !== null) {
// $qb->andWhere('s.id != :stockId')
// ->setParameter('stockId', $stockId);
// }
$stocks = $qb->getQuery()->getResult();
$qteAvailable = 0;
// dump($stocks);
/** @var Stock $stock */
foreach ($stocks as $stock) {
if ($stock->getQuantity() > 0) {
$qteAvailable += $stock->getQuantity();
}
}
$lastPmp = end($stocks);
$response = [
'lastPmp' => $lastPmp ? $lastPmp->getPmpAmount() : 0,
'qteAvailable' => $qteAvailable
];
return new JsonResponse($response, 200);
}
private function guessLot(Stock $stock):int
{
$uniqueCode = rand(1000000,9999999);
return '700'.str_shuffle($uniqueCode);
}
/**
* @Route("/update-remaining/{lotId}", name="app_stock_update_remaining")
*/
public function updateRemaining(
string $lotId,
Request $request,
StockRepository $lotRepository,
EntityManagerInterface $em,
CrmLogger $crmLogger
): JsonResponse {
// 1) Parse JSON
$payload = json_decode($request->getContent(), true);
if (!is_array($payload) || !array_key_exists('remainingQty', $payload)) {
return $this->json(['success' => false, 'error' => 'Paramètre "remainingQty" manquant.'], 400);
}
if (!is_numeric($payload['remainingQty'])) {
return $this->json(['success' => false, 'error' => '"remainingQty" doit être un nombre.'], 400);
}
$remainingQty = (int) $payload['remainingQty'];
if ($remainingQty < 0) {
return $this->json(['success' => false, 'error' => '"remainingQty" doit être ≥ 0.'], 422);
}
// 2) Retrouver le lot
// Si ton identifiant public est le N° de lot (ex: 8001751) stocké dans une colonne lotNumber :
$lot = $lotRepository->find($lotId);
// Sinon, si {lotId} est l'ID auto-incrémenté, dé-commente :
// $lot = $lotRepository->find($lotId);
if (!$lot) {
return $this->json(['success' => false, 'error' => 'Lot introuvable.'], 404);
}
// 3) Règles métier optionnelles (décommente/ajuste selon ton modèle)
// if ($remainingQty > $lot->getQtyInitial()) {
// return $this->json(['success' => false, 'error' => 'La quantité restante ne peut pas dépasser la quantité initiale.'], 422);
// }
// 4) Mise à jour + flush
$lot->setQuantity($remainingQty);
// Exemple : maj d’un flag "finished"
// $lot->setFinished($remainingQty === 0);
$em->flush();
$crmLogger->log(
'MODIFICATION',
'STOCK_LOT_UPDATE_REMAINING_SUCCESS',
sprintf('Mise à jour du lot ID : %s | Nouvelle quantité restante : %d', $lotId, $remainingQty),
$lot->getShop() ? $lot->getShop()->getLabel() : 'N/A',
$this->getUser() ? $this->getUser()->getUserIdentifier() : 'Anonyme'
);
// 5) Réponse
return $this->json([
'success' => true,
'lotId' => $lotId,
'remainingQty' => $remainingQty,
// 'finished' => $lot->isFinished(),
], 200);
}
}