src/Controller/API/WhiteMark/SaleOrderController.php line 152

Open in your IDE?
  1. <?php
  2. namespace App\Controller\API\WhiteMark;
  3. use App\Entity\SaleOrder;
  4. use App\Entity\User;
  5. use App\Exception\CartException;
  6. use App\Exception\CatalogueException;
  7. use App\Model\Product;
  8. use App\Services\API\VersioningService;
  9. use App\Services\Common\MailerService;
  10. use App\Services\DTV\YamlConfig\YamlReader;
  11. use App\Services\Front\CartService;
  12. use App\Services\Front\Catalogue\JsonCatalogueService;
  13. use App\Services\ProductService;
  14. use DateTime;
  15. use Doctrine\ORM\EntityManagerInterface;
  16. use Exception;
  17. use JMS\Serializer\DeserializationContext;
  18. use JMS\Serializer\SerializerInterface;
  19. use Nelmio\ApiDocBundle\Annotation\Model;
  20. use OpenApi\Annotations as OA;
  21. use Psr\Cache\InvalidArgumentException;
  22. use Symfony\Component\HttpFoundation\JsonResponse;
  23. use Symfony\Component\HttpFoundation\Request;
  24. use Symfony\Component\HttpFoundation\Response;
  25. use Symfony\Component\HttpKernel\KernelInterface;
  26. use Symfony\Component\Routing\Annotation\Route;
  27. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  28. use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
  29. use Symfony\Component\Validator\Validator\ValidatorInterface;
  30. use Symfony\Contracts\Cache\ItemInterface;
  31. use Symfony\Contracts\Cache\TagAwareCacheInterface;
  32. use Twig\Environment;
  33. class SaleOrderController extends ApiController
  34. {
  35. private JsonCatalogueService $catalogueService;
  36. private CartService $cartService;
  37. private ProductService $productService;
  38. private YamlReader $yamlReader;
  39. private Environment $twig;
  40. private MailerService $mailerService;
  41. public function __construct(
  42. EntityManagerInterface $em,
  43. SerializerInterface $serializer,
  44. UrlGeneratorInterface $urlGenerator,
  45. ValidatorInterface $validator,
  46. TagAwareCacheInterface $cache,
  47. KernelInterface $kernel,
  48. VersioningService $versioningService,
  49. JsonCatalogueService $catalogueService,
  50. CartService $cartService,
  51. ProductService $productService,
  52. YamlReader $yamlReader,
  53. Environment $twig,
  54. MailerService $mailerService
  55. ) {
  56. parent::__construct( $em, $serializer, $urlGenerator, $validator, $cache, $kernel, $versioningService );
  57. $this->catalogueService = $catalogueService;
  58. $this->cartService = $cartService;
  59. $this->productService = $productService;
  60. $this->yamlReader = $yamlReader;
  61. $this->twig = $twig;
  62. $this->mailerService = $mailerService;
  63. $this->cacheTagSlug = 'saleOrdersCache';
  64. }
  65. /**
  66. * Récupérer l'ensemble des commandes.
  67. *
  68. * @OA\Response(
  69. * response=200,
  70. * description="Retourne la liste des commandes",
  71. * @OA\JsonContent(
  72. * type="array",
  73. * @OA\Items(ref=@Model(type=SaleOrder::class, groups={"sale_order:list"}))
  74. * )
  75. * )
  76. * @OA\Parameter(
  77. * name="page",
  78. * in="query",
  79. * description="La page que l'on veut récupérer",
  80. * @OA\Schema(type="int")
  81. * )
  82. *
  83. * @OA\Parameter(
  84. * name="limit",
  85. * in="query",
  86. * description="Le nombre d'éléments que l'on veut récupérer",
  87. * @OA\Schema(type="int")
  88. * )
  89. * @OA\Tag(name="SaleOrders")
  90. *
  91. * @Route("/sale_orders", name="api_list_sale_order", methods={"GET"})
  92. *
  93. * @throws InvalidArgumentException
  94. */
  95. public function listSaleOrder( Request $request ): JsonResponse
  96. {
  97. $page = $request->get( 'page', 1 );
  98. $limit = $request->get( 'limit', 10 );
  99. $context = $this->getContext( [ "sale_order:list" ] );
  100. $idCache = "listSaleOrder-" . $page . "-" . $limit;
  101. $jsonOrders = $this->cache->get( $idCache, function ( ItemInterface $item ) use (
  102. $page,
  103. $limit,
  104. $context
  105. ) {
  106. $item->tag( $this->cacheTagSlug );
  107. $orders = $this->em->getRepository( SaleOrder::class )->findAllWithPagination( $page, $limit );
  108. return $this->serializer->serialize(
  109. $orders,
  110. 'json',
  111. $context,
  112. );
  113. } );
  114. return new JsonResponse( $jsonOrders, Response::HTTP_OK, [], TRUE );
  115. }
  116. /**
  117. * Récupérer l'ensemble des commandes qui ont été update depuis une date.
  118. *
  119. * @OA\Response(
  120. * response=200,
  121. * description="Retourne la liste des commandes updated depuis une date",
  122. * @OA\JsonContent(
  123. * type="array",
  124. * @OA\Items(ref=@Model(type=SaleOrder::class, groups={"sale_order:list"}))
  125. * )
  126. * )
  127. *
  128. * @OA\Parameter(
  129. * name="start",
  130. * in="query",
  131. * description="Date de début (format Y-m-d)",
  132. * @OA\Schema(type="date")
  133. * )
  134. * @OA\Tag(name="SaleOrders")
  135. *
  136. * @Route("/sale_orders_updated", name="api_list_sale_order_updated", methods={"GET"})
  137. *
  138. */
  139. public function listSaleOrderUpdated( Request $request ): JsonResponse
  140. {
  141. $date = $request->get( 'date', ( new DateTime() )->format( 'Y-m-d' ) );
  142. $context = $this->getContext( [ "sale_order:updated" ] );
  143. $orders = $this->em->getRepository( SaleOrder::class )->findUpdatedWithDate( $date );
  144. $jsonOrders = $this->serializer->serialize(
  145. $orders,
  146. 'json',
  147. $context,
  148. );
  149. return new JsonResponse( $jsonOrders, Response::HTTP_OK, [], TRUE );
  150. }
  151. /**
  152. * Créer une commande.
  153. *
  154. * @OA\Response(
  155. * response=201,
  156. * description="Retourne la commande créée",
  157. * ),
  158. *
  159. * @OA\RequestBody(
  160. * description="Commande à créer",
  161. * required=true,
  162. * @OA\MediaType(
  163. * mediaType="application/json",
  164. * @OA\Schema(ref=@Model(type=SaleOrder::class, groups={"sale_order:item"})),
  165. * ),
  166. * )
  167. *
  168. * @OA\Response(
  169. * response=400,
  170. * description="Données invalides"
  171. * ),
  172. *
  173. * @OA\Tag(name="SaleOrders")
  174. *
  175. * @Route("/sale_orders", name="api_create_sale_order", methods={"POST"})
  176. *
  177. * @throws Exception
  178. * @throws InvalidArgumentException
  179. */
  180. public function createSaleOrder( Request $request ): JsonResponse
  181. {
  182. /** @var SaleOrder $order */
  183. $context = DeserializationContext::create();
  184. $context->setAttribute('target', new SaleOrder()); // Force une nouvelle instance
  185. $order = $this->serializer->deserialize(
  186. $request->getContent(),
  187. SaleOrder::class,
  188. 'json',
  189. $context
  190. );
  191. if($order->getId() !== null){
  192. throw new Exception('Order already exists');
  193. }
  194. $orderRequest = json_decode($request->getContent(), true);
  195. if ( !$order->getUser() instanceof User ) {
  196. return new JsonResponse(
  197. $this->buildErrorContent( Response::HTTP_BAD_REQUEST, 'User not found', 'L\'utilisateur n\'existe pas' ),
  198. Response::HTTP_BAD_REQUEST,
  199. );
  200. }
  201. $total = 0;
  202. $alerts = [];
  203. foreach ( $order->getItems() as $item ) {
  204. $product = $this->catalogueService->findProductBySku( $item->getSku() );
  205. if ( !$product instanceof Product ) {
  206. return new JsonResponse(
  207. $this->buildErrorContent( Response::HTTP_BAD_REQUEST, 'Product not found', 'Le produit ' . $item->getSku() . ' n\'exsite pas' ),
  208. Response::HTTP_BAD_REQUEST,
  209. );
  210. }
  211. // check validité evasion/participants
  212. if($product->getType() === Product::TYPE_EVASION) {
  213. foreach ($orderRequest['items'] as $orderRequestItem) {
  214. if($product->getSku() === $orderRequestItem['sku']) {
  215. if(!isset($orderRequestItem['participants']) || !count($orderRequestItem['participants'])) {
  216. return new JsonResponse(
  217. $this->buildErrorContent( Response::HTTP_BAD_REQUEST, 'Participants required', 'Veuillez renseigner des participants pour le produit ' . $item->getSku() ),
  218. Response::HTTP_BAD_REQUEST,
  219. );
  220. }
  221. }
  222. }
  223. $nbAdultes = (int) $product->getCombinationValueByKey('nombreadultes');
  224. $nbEnfants = (int) $product->getCombinationValueByKey('nombreenfants');
  225. $countAdultes = 0;
  226. $countEnfants = 0;
  227. foreach ($item->getParticipants() as $participant) {
  228. if($participant->getTypeParticipant() === 'A') {
  229. $countAdultes ++;
  230. }
  231. if($participant->getTypeParticipant() === 'E') {
  232. $countEnfants ++;
  233. }
  234. }
  235. if($nbAdultes !== $countAdultes) {
  236. return new JsonResponse(
  237. $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)'),
  238. Response::HTTP_BAD_REQUEST,
  239. );
  240. }
  241. if($nbEnfants !== $countEnfants) {
  242. return new JsonResponse(
  243. $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)'),
  244. Response::HTTP_BAD_REQUEST,
  245. );
  246. }
  247. }
  248. try {
  249. $sku = $product->getSku();
  250. $alertStock = $product->getStockAlert();
  251. $stock = $product->getStock() - $this->productService->getStockPending( $sku );
  252. $item->setSaleOrder( $order )
  253. ->setName( $product->getName() )
  254. ->setDescription( $product->getDescription() )
  255. ->setReference( $product->getReference() )
  256. ->setGamme( $product->getType() )
  257. ->setPriceHT( $product->getSalePrice() )
  258. ->setPriceTTC( $product->getPriceTTC() )
  259. ->setTaxableAmount( $product->getTaxableAmount() )
  260. ->setUnitPoint( $this->cartService->getPointByCartItem( $product->getSku() ) )
  261. ->setImageUrl( $product->getImages()[ 0 ][ 'name' ] ?? '' )
  262. ;
  263. if($product->getType() === Product::TYPE_EVASION){
  264. foreach ($item->getParticipants() as $participant) {
  265. $participant->setSaleOrderItem($item);
  266. }
  267. }
  268. if ( ( $stock + $item->getQuantity() ) <= $alertStock ) {
  269. $product->setStock( $stock - $item->getQuantity() );
  270. $alerts[ $sku ] = $product;
  271. }
  272. }
  273. catch ( CartException|CatalogueException $e ) {
  274. return new JsonResponse(
  275. $this->buildErrorContent( Response::HTTP_BAD_REQUEST, 'Error when creating the order', $e->getMessage() ),
  276. Response::HTTP_BAD_REQUEST,
  277. );
  278. }
  279. $total += $item->getUnitPoint() * $item->getQuantity();
  280. }
  281. // TODO CALCULER LE SHIPPING PRICE
  282. $order->setTotal( $total )
  283. ->setShippingPrice( 0 )
  284. ->setShippingMethod( 'NONE' )
  285. ;
  286. // On vérifie les erreurs
  287. $errors = $this->validator->validate( $order );
  288. if ( $errors->count() > 0 ) {
  289. return new JsonResponse(
  290. $this->buildErrorContent( Response::HTTP_BAD_REQUEST, 'Validation Failed', $errors ),
  291. Response::HTTP_BAD_REQUEST,
  292. );
  293. }
  294. $this->em->persist( $order );
  295. $this->em->flush();
  296. $jsonOrder = $this->serializer->serialize( $order, 'json', $this->getContext( [ "sale_order:item" ] ) );
  297. $location = $this->urlGenerator->generate( 'api_show_sale_order', [ 'id' => $order->getId() ], UrlGeneratorInterface::ABSOLUTE_URL );
  298. // On vide le cache
  299. $this->clearCache();
  300. // @TODO : on envoie un email si au moins l'un des articles de la commande a : stock calculé <= stock alerte
  301. // $mailer = $this->yamlReader->getMailer();
  302. // $admins = $mailer[ 'stock_alert' ] ?? [];
  303. //
  304. // if ( !empty( $alerts ) && !empty( $admins ) ) {
  305. // foreach ( $admins as $admin ) {
  306. // $html = $this->twig->render( 'back/product/email/alert_stock_v2.html.twig', [
  307. // 'admin' => $admin,
  308. // 'alerts' => $alerts,
  309. // ] );
  310. // $this->mailerService->sendMailRaw(
  311. // 'from',
  312. // $admin[ 'email' ],
  313. // 'Alerte stock',
  314. // $html,
  315. // );
  316. // }
  317. // }
  318. return new JsonResponse( $jsonOrder, Response::HTTP_CREATED, [ "Location" => $location ], TRUE );
  319. }
  320. /**
  321. * Récupérer une commande par son id.
  322. *
  323. * @OA\Response(
  324. * response=200,
  325. * description="Retourne une commande",
  326. * @OA\JsonContent(
  327. * type="array",
  328. * @OA\Items(ref=@Model(type=SaleOrder::class, groups={"sale_order:item"}))
  329. * )
  330. * )
  331. * @OA\Tag(name="SaleOrders")
  332. *
  333. * @Route("/sale_orders/{id}", name="api_show_sale_order", methods={"GET"})
  334. */
  335. public function showSaleOrder( string $id ): JsonResponse
  336. {
  337. $order = $this->em->getRepository( SaleOrder::class )->find( $id );
  338. if ( !$order instanceof SaleOrder ) {
  339. return $this->getResponseEntityNotFound( 'SaleOrder' );
  340. }
  341. $jsonOrder = $this->serializer->serialize( $order, 'json', $this->getContext( [ "sale_order:item" ] ) );
  342. return new JsonResponse( $jsonOrder, Response::HTTP_OK, [], TRUE );
  343. }
  344. }