<?php
namespace App\Controller\API\WhiteMark;
use App\Entity\SaleOrder;
use App\Entity\User;
use App\Exception\CartException;
use App\Exception\CatalogueException;
use App\Model\Product;
use App\Services\API\VersioningService;
use App\Services\Common\MailerService;
use App\Services\DTV\YamlConfig\YamlReader;
use App\Services\Front\CartService;
use App\Services\Front\Catalogue\JsonCatalogueService;
use App\Services\ProductService;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use JMS\Serializer\DeserializationContext;
use JMS\Serializer\SerializerInterface;
use Nelmio\ApiDocBundle\Annotation\Model;
use OpenApi\Annotations as OA;
use Psr\Cache\InvalidArgumentException;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Contracts\Cache\TagAwareCacheInterface;
use Twig\Environment;
class SaleOrderController extends ApiController
{
private JsonCatalogueService $catalogueService;
private CartService $cartService;
private ProductService $productService;
private YamlReader $yamlReader;
private Environment $twig;
private MailerService $mailerService;
public function __construct(
EntityManagerInterface $em,
SerializerInterface $serializer,
UrlGeneratorInterface $urlGenerator,
ValidatorInterface $validator,
TagAwareCacheInterface $cache,
KernelInterface $kernel,
VersioningService $versioningService,
JsonCatalogueService $catalogueService,
CartService $cartService,
ProductService $productService,
YamlReader $yamlReader,
Environment $twig,
MailerService $mailerService
) {
parent::__construct( $em, $serializer, $urlGenerator, $validator, $cache, $kernel, $versioningService );
$this->catalogueService = $catalogueService;
$this->cartService = $cartService;
$this->productService = $productService;
$this->yamlReader = $yamlReader;
$this->twig = $twig;
$this->mailerService = $mailerService;
$this->cacheTagSlug = 'saleOrdersCache';
}
/**
* Récupérer l'ensemble des commandes.
*
* @OA\Response(
* response=200,
* description="Retourne la liste des commandes",
* @OA\JsonContent(
* type="array",
* @OA\Items(ref=@Model(type=SaleOrder::class, groups={"sale_order:list"}))
* )
* )
* @OA\Parameter(
* name="page",
* in="query",
* description="La page que l'on veut récupérer",
* @OA\Schema(type="int")
* )
*
* @OA\Parameter(
* name="limit",
* in="query",
* description="Le nombre d'éléments que l'on veut récupérer",
* @OA\Schema(type="int")
* )
* @OA\Tag(name="SaleOrders")
*
* @Route("/sale_orders", name="api_list_sale_order", methods={"GET"})
*
* @throws InvalidArgumentException
*/
public function listSaleOrder( Request $request ): JsonResponse
{
$page = $request->get( 'page', 1 );
$limit = $request->get( 'limit', 10 );
$context = $this->getContext( [ "sale_order:list" ] );
$idCache = "listSaleOrder-" . $page . "-" . $limit;
$jsonOrders = $this->cache->get( $idCache, function ( ItemInterface $item ) use (
$page,
$limit,
$context
) {
$item->tag( $this->cacheTagSlug );
$orders = $this->em->getRepository( SaleOrder::class )->findAllWithPagination( $page, $limit );
return $this->serializer->serialize(
$orders,
'json',
$context,
);
} );
return new JsonResponse( $jsonOrders, Response::HTTP_OK, [], TRUE );
}
/**
* Récupérer l'ensemble des commandes qui ont été update depuis une date.
*
* @OA\Response(
* response=200,
* description="Retourne la liste des commandes updated depuis une date",
* @OA\JsonContent(
* type="array",
* @OA\Items(ref=@Model(type=SaleOrder::class, groups={"sale_order:list"}))
* )
* )
*
* @OA\Parameter(
* name="start",
* in="query",
* description="Date de début (format Y-m-d)",
* @OA\Schema(type="date")
* )
* @OA\Tag(name="SaleOrders")
*
* @Route("/sale_orders_updated", name="api_list_sale_order_updated", methods={"GET"})
*
*/
public function listSaleOrderUpdated( Request $request ): JsonResponse
{
$date = $request->get( 'date', ( new DateTime() )->format( 'Y-m-d' ) );
$context = $this->getContext( [ "sale_order:updated" ] );
$orders = $this->em->getRepository( SaleOrder::class )->findUpdatedWithDate( $date );
$jsonOrders = $this->serializer->serialize(
$orders,
'json',
$context,
);
return new JsonResponse( $jsonOrders, Response::HTTP_OK, [], TRUE );
}
/**
* Créer une commande.
*
* @OA\Response(
* response=201,
* description="Retourne la commande créée",
* ),
*
* @OA\RequestBody(
* description="Commande à créer",
* required=true,
* @OA\MediaType(
* mediaType="application/json",
* @OA\Schema(ref=@Model(type=SaleOrder::class, groups={"sale_order:item"})),
* ),
* )
*
* @OA\Response(
* response=400,
* description="Données invalides"
* ),
*
* @OA\Tag(name="SaleOrders")
*
* @Route("/sale_orders", name="api_create_sale_order", methods={"POST"})
*
* @throws Exception
* @throws InvalidArgumentException
*/
public function createSaleOrder( Request $request ): JsonResponse
{
/** @var SaleOrder $order */
$context = DeserializationContext::create();
$context->setAttribute('target', new SaleOrder()); // Force une nouvelle instance
$order = $this->serializer->deserialize(
$request->getContent(),
SaleOrder::class,
'json',
$context
);
if($order->getId() !== null){
throw new Exception('Order already exists');
}
$orderRequest = json_decode($request->getContent(), true);
if ( !$order->getUser() instanceof User ) {
return new JsonResponse(
$this->buildErrorContent( Response::HTTP_BAD_REQUEST, 'User not found', 'L\'utilisateur n\'existe pas' ),
Response::HTTP_BAD_REQUEST,
);
}
$total = 0;
$alerts = [];
foreach ( $order->getItems() as $item ) {
$product = $this->catalogueService->findProductBySku( $item->getSku() );
if ( !$product instanceof Product ) {
return new JsonResponse(
$this->buildErrorContent( Response::HTTP_BAD_REQUEST, 'Product not found', 'Le produit ' . $item->getSku() . ' n\'exsite pas' ),
Response::HTTP_BAD_REQUEST,
);
}
// check validité evasion/participants
if($product->getType() === Product::TYPE_EVASION) {
foreach ($orderRequest['items'] as $orderRequestItem) {
if($product->getSku() === $orderRequestItem['sku']) {
if(!isset($orderRequestItem['participants']) || !count($orderRequestItem['participants'])) {
return new JsonResponse(
$this->buildErrorContent( Response::HTTP_BAD_REQUEST, 'Participants required', 'Veuillez renseigner des participants pour le produit ' . $item->getSku() ),
Response::HTTP_BAD_REQUEST,
);
}
}
}
$nbAdultes = (int) $product->getCombinationValueByKey('nombreadultes');
$nbEnfants = (int) $product->getCombinationValueByKey('nombreenfants');
$countAdultes = 0;
$countEnfants = 0;
foreach ($item->getParticipants() as $participant) {
if($participant->getTypeParticipant() === 'A') {
$countAdultes ++;
}
if($participant->getTypeParticipant() === 'E') {
$countEnfants ++;
}
}
if($nbAdultes !== $countAdultes) {
return new JsonResponse(
$this->buildErrorContent( Response::HTTP_BAD_REQUEST, 'Participants "A" error', 'Veuillez renseigner le bon nombre de participant type "A" pour le produit ' . $item->getSku() . ' ('. $nbAdultes .' attendus)'),
Response::HTTP_BAD_REQUEST,
);
}
if($nbEnfants !== $countEnfants) {
return new JsonResponse(
$this->buildErrorContent( Response::HTTP_BAD_REQUEST, 'Participants "E" error', 'Veuillez renseigner le bon nombre de participant type "E" pour le produit ' . $item->getSku() . ' ('. $nbEnfants .' attendus)'),
Response::HTTP_BAD_REQUEST,
);
}
}
try {
$sku = $product->getSku();
$alertStock = $product->getStockAlert();
$stock = $product->getStock() - $this->productService->getStockPending( $sku );
$item->setSaleOrder( $order )
->setName( $product->getName() )
->setDescription( $product->getDescription() )
->setReference( $product->getReference() )
->setGamme( $product->getType() )
->setPriceHT( $product->getSalePrice() )
->setPriceTTC( $product->getPriceTTC() )
->setTaxableAmount( $product->getTaxableAmount() )
->setUnitPoint( $this->cartService->getPointByCartItem( $product->getSku() ) )
->setImageUrl( $product->getImages()[ 0 ][ 'name' ] ?? '' )
;
if($product->getType() === Product::TYPE_EVASION){
foreach ($item->getParticipants() as $participant) {
$participant->setSaleOrderItem($item);
}
}
if ( ( $stock + $item->getQuantity() ) <= $alertStock ) {
$product->setStock( $stock - $item->getQuantity() );
$alerts[ $sku ] = $product;
}
}
catch ( CartException|CatalogueException $e ) {
return new JsonResponse(
$this->buildErrorContent( Response::HTTP_BAD_REQUEST, 'Error when creating the order', $e->getMessage() ),
Response::HTTP_BAD_REQUEST,
);
}
$total += $item->getUnitPoint() * $item->getQuantity();
}
// TODO CALCULER LE SHIPPING PRICE
$order->setTotal( $total )
->setShippingPrice( 0 )
->setShippingMethod( 'NONE' )
;
// On vérifie les erreurs
$errors = $this->validator->validate( $order );
if ( $errors->count() > 0 ) {
return new JsonResponse(
$this->buildErrorContent( Response::HTTP_BAD_REQUEST, 'Validation Failed', $errors ),
Response::HTTP_BAD_REQUEST,
);
}
$this->em->persist( $order );
$this->em->flush();
$jsonOrder = $this->serializer->serialize( $order, 'json', $this->getContext( [ "sale_order:item" ] ) );
$location = $this->urlGenerator->generate( 'api_show_sale_order', [ 'id' => $order->getId() ], UrlGeneratorInterface::ABSOLUTE_URL );
// On vide le cache
$this->clearCache();
// @TODO : on envoie un email si au moins l'un des articles de la commande a : stock calculé <= stock alerte
// $mailer = $this->yamlReader->getMailer();
// $admins = $mailer[ 'stock_alert' ] ?? [];
//
// if ( !empty( $alerts ) && !empty( $admins ) ) {
// foreach ( $admins as $admin ) {
// $html = $this->twig->render( 'back/product/email/alert_stock_v2.html.twig', [
// 'admin' => $admin,
// 'alerts' => $alerts,
// ] );
// $this->mailerService->sendMailRaw(
// 'from',
// $admin[ 'email' ],
// 'Alerte stock',
// $html,
// );
// }
// }
return new JsonResponse( $jsonOrder, Response::HTTP_CREATED, [ "Location" => $location ], TRUE );
}
/**
* Récupérer une commande par son id.
*
* @OA\Response(
* response=200,
* description="Retourne une commande",
* @OA\JsonContent(
* type="array",
* @OA\Items(ref=@Model(type=SaleOrder::class, groups={"sale_order:item"}))
* )
* )
* @OA\Tag(name="SaleOrders")
*
* @Route("/sale_orders/{id}", name="api_show_sale_order", methods={"GET"})
*/
public function showSaleOrder( string $id ): JsonResponse
{
$order = $this->em->getRepository( SaleOrder::class )->find( $id );
if ( !$order instanceof SaleOrder ) {
return $this->getResponseEntityNotFound( 'SaleOrder' );
}
$jsonOrder = $this->serializer->serialize( $order, 'json', $this->getContext( [ "sale_order:item" ] ) );
return new JsonResponse( $jsonOrder, Response::HTTP_OK, [], TRUE );
}
}