src/Controller/Subscription/UserSubscriptionController.php line 434

Open in your IDE?
  1. <?php
  2. namespace App\Controller\Subscription;
  3. use App\Entity\Category;
  4. use App\Entity\Discount;
  5. use App\Entity\SubjectLevel;
  6. use App\Entity\SubscriptionOrder;
  7. use App\Entity\Teacher;
  8. use App\Entity\User;
  9. use App\Form\SubscriptionPrivateOrderType;
  10. use App\Helper\BookHelper;
  11. use App\Helper\CourseHelper;
  12. use App\Helper\SubscriptionHelper;
  13. use App\Notification\EmailNotification;
  14. use App\Notification\SlackClient;
  15. use App\Repository\DiscountRepository;
  16. use App\Repository\HolidayRepository;
  17. use App\Repository\PaidCourseListRepository;
  18. use App\Repository\PlanRepository;
  19. use App\Repository\SubjectLevelRepository;
  20. use App\Repository\SubjectRepository;
  21. use App\Repository\SubscriptionOrderRepository;
  22. use App\Repository\TeacherScheduleRepository;
  23. use App\Service\Currency\CurrencyChangeService;
  24. use App\Service\Localisation\CountryInfoService;
  25. use App\Service\Localisation\GeoIPService;
  26. use App\StripeClient;
  27. use DateTimeImmutable;
  28. use Doctrine\ORM\EntityManagerInterface;
  29. use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
  30. use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
  31. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  32. use Symfony\Component\HttpFoundation\RedirectResponse;
  33. use Symfony\Component\HttpFoundation\Request;
  34. use Symfony\Component\HttpFoundation\Response;
  35. use Symfony\Component\HttpFoundation\Session\Session;
  36. use Symfony\Component\Routing\Annotation\Route;
  37. use Symfony\Polyfill\Intl\Icu\Currencies;
  38. use Symfony\Polyfill\Intl\Icu\NumberFormatter;
  39. /**
  40. * @Route("user/subscription")
  41. * @Security("is_granted('ROLE_USER')")
  42. */
  43. class UserSubscriptionController extends AbstractController
  44. {
  45. private $subscriptionHelper;
  46. private $bookHelper;
  47. //Acheter OK
  48. //Voir
  49. //Editer
  50. //Renouveler OK
  51. //Reactiver
  52. //Supprimer
  53. public function __construct(SubscriptionHelper $subscriptionHelper, BookHelper $bookHelper)
  54. {
  55. $this->subscriptionHelper = $subscriptionHelper;
  56. $this->bookHelper = $bookHelper;
  57. }
  58. /**
  59. * @Route("/{category}/{teacher}/checkout", name="subscription_checkout")
  60. * @ParamConverter("category", options={"mapping": {"category" : "name"}})
  61. * @ParamConverter("teacher", options={"mapping": {"teacher" : "id"}})
  62. */
  63. public function orderSubscription(Request $request, Category $category, Teacher $teacher, SubscriptionOrderRepository $subscriptionOrderRepository,
  64. SubjectRepository $subjectRepository, DiscountRepository $discountRepository, StripeClient $stripeClient,
  65. SubjectLevelRepository $subjectLevelRepository, CurrencyChangeService $currencyChangeService
  66. ) {
  67. /** @var User $user */
  68. $user = $this->getUser();
  69. $parameter = $request->query->get('d');
  70. /** @var Discount $discount */
  71. $discount = $discountRepository->findOneBy([
  72. 'urlParameter' => $parameter,
  73. 'isForGroup' => false,
  74. ]);
  75. if ($discount) {
  76. $couponStripeName = $discount->getStripeName();
  77. $discountType = $discount->getType();
  78. try {
  79. $stripeCoupon = $stripeClient->findCoupon($couponStripeName);
  80. if ($stripeCoupon->valid) {
  81. $isDiscount = true;
  82. $request->getSession()->set('code-subscription', $stripeCoupon);
  83. } else {
  84. $isDiscount = false;
  85. $discountAmount = 0;
  86. $discountType = null;
  87. $stripeCoupon = false;
  88. $this->addFlash(
  89. 'error',
  90. ' Coupon invalid'
  91. );
  92. }
  93. } catch (\Stripe\Exception\InvalidRequestException $e) {
  94. $isDiscount = false;
  95. $discountAmount = 0;
  96. $discountType = null;
  97. $stripeCoupon = false;
  98. $this->addFlash(
  99. 'error',
  100. $e.' Coupon invalid'
  101. );
  102. }
  103. } else {
  104. $stripeCoupon = false;
  105. $isDiscount = false;
  106. $discountAmount = 0;
  107. $discountType = null;
  108. }
  109. //reception possible : beginner, intermediate, advanced
  110. $autoAssignLevel = $request->query->get('level');
  111. if ($autoAssignLevel !== "intermediate" && $autoAssignLevel !== "advanced" && $autoAssignLevel !== "beginner") {
  112. $autoAssignLevel = null;
  113. }
  114. //reception possible: "0", "1"
  115. $isChild = $request->query->get('child');
  116. if ($isChild != "0" && $isChild != "1") {
  117. $isChild = null;
  118. }
  119. $categoryEn = $category->getNameEn();
  120. $error = false;
  121. $currency = $currencyChangeService->getCurrencyChangeForView($user);
  122. //$stripeCoupon = $request->getSession()->get('code-subscription', false);
  123. $subscriptionOrder = new SubscriptionOrder();
  124. $subscriptionOrder->setTeacher($teacher);
  125. $subscriptionOrder->setCategory($category);
  126. $subscriptionOrder->setUser($user);
  127. $form = $this->createForm(SubscriptionPrivateOrderType::class, $subscriptionOrder, [
  128. 'locale' => $request->getLocale(),
  129. 'isChild' => $isChild,
  130. ]);
  131. $form->handleRequest($request);
  132. if ($form->isSubmitted() && $form->isValid()) {
  133. $request->getSession()->remove('code-subscription');
  134. /** @var SubscriptionOrder $subscriptionOrder */
  135. $subscriptionOrder = $form->getData();
  136. //Reception data possible : all, chafawi, naho, qiraa, sarf, balagha,child-program, quran,tajwid, quran-child
  137. $subscriptionData = $request->request->all('subscription_private_order');
  138. $requestedProgram = $subscriptionData ? $subscriptionData['program'] : null;
  139. $subscriptionOrder->setPurchaseDate(new \DateTimeImmutable())
  140. ->setTotalLostMinutes(0)
  141. ->setRamadanCreditAmount(0);
  142. foreach ($subscriptionOrder->getSchedules() as $teacherSchedule) {
  143. $repeatedSubscription = $subscriptionOrderRepository->findRepeatedOrder($teacherSchedule);
  144. if ($repeatedSubscription) {
  145. $this->addFlash(
  146. 'error',
  147. "Ce Planning vient d'être réservé, si vous venez de le reserver, il se peut que cette erreur vienne du fait que vous ayez cliquez plusieurs fois sur le bouton s'inscrire"
  148. );
  149. return $this->redirectToRoute(
  150. 'schedule_list',
  151. [
  152. 'category' => $category,
  153. 'gender' => $user->getGender()->getName(),
  154. '_locale' => $request->getLocale(),
  155. ]
  156. );
  157. }
  158. }
  159. $plan = $subscriptionOrder->getPlan();
  160. if ($stripeCoupon) {
  161. if ($plan->getFrequency() != $discount->getPlan()) {
  162. $stripeCoupon = false;
  163. }
  164. }
  165. $token = $request->get('stripeToken');
  166. $quantity = $subscriptionOrder->getNbOfScheduleChoose();
  167. $participant = $subscriptionOrder->getParticipant();
  168. $schedules = $subscriptionOrder->getSchedules();
  169. // Get currency information for multi-currency support
  170. $currencyInfo = $currencyChangeService->getCurrencyChangeForView($user);
  171. $currency = $currencyInfo['currencyCode'] ? $currencyInfo['currencyCode'] : 'EUR';
  172. $convertedAmount = null;
  173. if ($currency !== 'EUR') {
  174. try {
  175. $convertedAmount = $currencyChangeService->convertAmount($plan->getPrice(), $currency);
  176. } catch (\Exception $e) {
  177. // If conversion fails, fallback to EUR
  178. $currency = 'EUR';
  179. $convertedAmount = $plan->getPrice();
  180. }
  181. }
  182. else {
  183. $convertedAmount = $plan->getPrice();// If currency is EUR, use the base price
  184. }
  185. try {
  186. $stripeSubscription = $this->subscriptionHelper->chargeCustomer($token, $user, $plan, $quantity, $stripeCoupon, $schedules, $currency, $convertedAmount);
  187. } catch (\Stripe\Exception\CardException $e) {
  188. //error_log("A payment error occurred: {$e->getError()->message}");
  189. $error = 'Il y a un problème pour charger votre carte: '.$e->getError()->message;
  190. } catch (\Stripe\Exception\InvalidRequestException $e) {
  191. $error = 'Il y a un problème pour charger votre carte: '.$e->getError()->message;
  192. } catch (\Exception $e) {
  193. //error_log("Another problem occurred, maybe unrelated to Stripe.");
  194. $error = 'Il y a un problème pour charger votre carte: '.$e;
  195. }
  196. /*catch (\Stripe\Error\Card $e) {
  197. $error = 'Il y a un problème pour charger votre carte: '.$e->getMessage();
  198. }
  199. */
  200. if ( ! $error) {
  201. if ($stripeSubscription->status == 'active') {
  202. $this->addFlash(
  203. 'success',
  204. 'Votre inscription a été reçue. Elle sera validée lorsque le paiement sera confirmé. Vous recevrez un email et vos cours seront visibles dans votre espace membre'
  205. );
  206. // $requestedProgram à lier avec les entités Subject dans la BDD avec le tag
  207. //all -> alKunuz
  208. //chafawi -> oral
  209. //naho -> ajromiyah
  210. //qiraa -> texteMedine
  211. //sarf -> sarf-private
  212. //balagha -> balagha
  213. //child-program -> tresorsEnfant
  214. //quran -> coranComplet
  215. //tajwid -> tajweed
  216. //quran-child -> coranCompletChildren
  217. //nouraniyah -> an Nouraniyah
  218. $level = null;
  219. if ($requestedProgram == "all") {
  220. $subject = $subjectRepository->findOneBy(['tagName' => "alKaamil"]);
  221. } elseif ($requestedProgram == "chafawi") {
  222. $subject = $subjectRepository->findOneBy(['tagName' => "oralExpression"]);
  223. } elseif ($requestedProgram == "naho") {
  224. $subject = $subjectRepository->findOneBy(['tagName' => "ajromiyah"]);
  225. /** @var SubjectLevel $level */
  226. $level = $subjectLevelRepository->findOneBy([
  227. 'subject' => $subject,
  228. 'level' => 1,
  229. ]);
  230. } elseif ($requestedProgram == "qiraa") {
  231. $subject = $subjectRepository->findOneBy(['tagName' => "medinaTexte"]);
  232. /** @var SubjectLevel $level */
  233. $level = $subjectLevelRepository->findOneBy([
  234. 'subject' => $subject,
  235. 'level' => 1,
  236. ]);
  237. } elseif ($requestedProgram == "sarf") {
  238. //pas de niveau
  239. $subject = $subjectRepository->findOneBy(['tagName' => "sarf"]);
  240. } elseif ($requestedProgram == "balagha") {
  241. //pas de niveau
  242. $subject = $subjectRepository->findOneBy(['tagName' => "balagha"]);
  243. } elseif ($requestedProgram == "child-program") {
  244. // a des niveaux mais dois passser un test
  245. $subject = $subjectRepository->findOneBy(['tagName' => "tresorEnfant"]);
  246. } elseif ($requestedProgram == "quran") {
  247. $subject = $subjectRepository->findOneBy(['tagName' => "coranComplet"]);
  248. } elseif ($requestedProgram == "tajwid") {
  249. $subject = $subjectRepository->findOneBy(['tagName' => "tajweedBeginner"]);
  250. } elseif ($requestedProgram == "quran-child") {
  251. $subject = $subjectRepository->findOneBy(['tagName' => "coranCompletChildren"]);;
  252. } elseif ($requestedProgram == "nouraniyah") {
  253. $subject = $subjectRepository->findOneBy(['tagName' => "nouraniyah"]);
  254. } else {
  255. if ($subscriptionOrder->getCategory()->getNameEn() == 'Arabic') {
  256. if ($isChild) {
  257. $subject = $subjectRepository->findOneBy(['tagName' => "tresorEnfant"]);
  258. } else {
  259. $subject = $subjectRepository->findOneBy(['tagName' => "alKaamil"]);
  260. }
  261. } else {
  262. if ($isChild) {
  263. $subject = $subjectRepository->findOneBy(['tagName' => "coranCompletChildren"]);
  264. } else {
  265. $subject = $subjectRepository->findOneBy(['tagName' => "coranComplet"]);
  266. }
  267. }
  268. }
  269. if ($requestedProgram == null) {
  270. if ($subscriptionOrder->getCategory()->getNameEn() == 'Arabic') {
  271. if ($isChild) {
  272. $subject = $subjectRepository->findOneBy(['tagName' => "tresorEnfant"]);
  273. } else {
  274. $subject = $subjectRepository->findOneBy(['tagName' => "alKaamil"]);
  275. }
  276. } else {
  277. if ($isChild) {
  278. $subject = $subjectRepository->findOneBy(['tagName' => "coranCompletChildren"]);
  279. } else {
  280. $subject = $subjectRepository->findOneBy(['tagName' => "coranComplet"]);
  281. }
  282. }
  283. }
  284. //TODO utiliser Messenger
  285. $subscriptionOrder = $this->subscriptionHelper->addSubscriptionInBDD(
  286. $subscriptionOrder->getUser(),
  287. $plan,
  288. $schedules,
  289. $category,
  290. $teacher,
  291. $quantity,
  292. $stripeSubscription,
  293. $subject,
  294. $level,
  295. $participant
  296. );
  297. //TODO utiliser Messenger
  298. $this->subscriptionHelper->sendNotificationForNewSubscription($subscriptionOrder);
  299. if ( ! $subject) {
  300. //TODO utiliser Messenger
  301. $this->subscriptionHelper->sendAlertNoSubject($subscriptionOrder);
  302. }
  303. //TODO utiliser Messenger
  304. if ($requestedProgram != "quran" and $requestedProgram != "quran-child") {
  305. $this->bookHelper->getChoosenBookForSubscription($subject, $subscriptionOrder, $level);
  306. }
  307. return $this->redirectToRoute(
  308. 'confirmation_purchase',
  309. [
  310. 'userId' => $user->getId(),
  311. 'orderType' => 'subscription',
  312. 'orderId' => $subscriptionOrder->getId(),
  313. '_locale' => $request->getLocale(),
  314. ]
  315. );
  316. } else {
  317. $this->addFlash('error', "Votre Paiement n'a pas été accepté par votre banque");
  318. return $this->redirectToRoute(
  319. 'subscription_checkout',
  320. [
  321. 'category' => $category->getName(),
  322. 'teacher' => $teacher->getId(),
  323. '_locale' => $request->getLocale(),
  324. ]
  325. );
  326. }
  327. } else {
  328. $this->addFlash('error', $error);
  329. return $this->redirectToRoute(
  330. 'subscription_checkout',
  331. [
  332. 'category' => $category->getName(),
  333. 'teacher' => $teacher->getId(),
  334. '_locale' => $request->getLocale(),
  335. ]
  336. );
  337. }
  338. }
  339. return $this->render(
  340. 'schedule/purchase.html.twig',
  341. [
  342. 'form' => $form->createView(),
  343. 'categoryName' => $category,
  344. 'teacher' => $teacher,
  345. 'user' => $user,
  346. 'stripe_public_key' => $this->getParameter('stripe_public_key'),
  347. 'local' => $this->getParameter('locale'),
  348. 'errorCard' => $error,
  349. 'categoryEn' => $categoryEn,
  350. 'code' => ($stripeCoupon !== false) ? $stripeCoupon->percent_off : 0,
  351. 'isChild' => $isChild,
  352. 'discount' => $discount,
  353. 'isDiscount' => $isDiscount,
  354. 'currency' => $currency,
  355. ]
  356. );
  357. }
  358. /**
  359. * @Route("/{id}/resubscribe", name="subscription_resubscribe")
  360. * @param SubscriptionOrder $oldSubscriptionOrder
  361. * @param PlanRepository $planRepository
  362. * @param Request $request
  363. * @param Session $sessionSymfo
  364. *
  365. * @return RedirectResponse|Response
  366. */
  367. public function reSubscribe(SubscriptionOrder $oldSubscriptionOrder, PlanRepository $planRepository, Request $request, Session $sessionSymfo, CurrencyChangeService $currencyChangeService
  368. ) {
  369. /** @var User $user */
  370. $user = $oldSubscriptionOrder->getUser();
  371. //Montrer les nouveaux plan qui ne sont pas de la même fréquence. Au cas où l'élève souhaite changer de rythme (il passe au nouveau tarif, je ne montre donc pas les anciens plans)
  372. $activePlan = $planRepository->getPlanToEditFrequencyFromOldToNew($oldSubscriptionOrder->getCategory(), $oldSubscriptionOrder->getPlan()->getFrequency());
  373. $error = false;
  374. if ($sessionSymfo->get('code-subscription')) {
  375. $stripeCoupon = $sessionSymfo->get('code-subscription');
  376. $code = $stripeCoupon->percent_off;
  377. } else {
  378. $stripeCoupon = false;
  379. $code = 0;
  380. }
  381. $currency = $currencyChangeService->getCurrencyChangeForView($this->getUser());
  382. if ($request->isMethod('POST')) {
  383. $token = $request->request->get('stripeToken');
  384. $quantity = $request->get('quantity');
  385. $planStripeId = $request->get('frequency');
  386. $schedules = $request->get('schedule');
  387. $participant = $oldSubscriptionOrder->getParticipant();
  388. if ( ! $quantity) {
  389. $this->addFlash('error', 'Ne PAS taper sur la touche entrer de votre clavier pour envoyer le formulaire, mais CLIQUEZ sur le boutton "S\'inscrire" avec votre souris ');
  390. return $this->redirectToRoute(
  391. 'subscription_resubscribe',
  392. [
  393. 'id' => $oldSubscriptionOrder->getId(),
  394. '_locale' => $request->getLocale(),
  395. ]
  396. );
  397. }
  398. if ($planStripeId == null) {
  399. $this->addFlash('error', "Merci de choisir un rythme d'abonnement");
  400. return $this->redirectToRoute(
  401. 'subscription_resubscribe',
  402. [
  403. 'id' => $oldSubscriptionOrder->getId(),
  404. '_locale' => $request->getLocale(),
  405. ]
  406. );
  407. }
  408. $plan = $planRepository->findOneBy(
  409. [
  410. 'stripeId' => $planStripeId,
  411. ]
  412. );
  413. if ($stripeCoupon != false) {
  414. if ($plan->getFrequency() != "mensuel") {
  415. $stripeCoupon = false;
  416. }
  417. }
  418. $subject = $oldSubscriptionOrder->getSubject();
  419. $level = $oldSubscriptionOrder->getSubjectLevel();
  420. // Get currency information for multi-currency support
  421. $currencyInfo = $currencyChangeService->getCurrencyChangeForView($user);
  422. $currency = $currencyInfo['currencyCode'] ? $currencyInfo['currencyCode'] : 'EUR';
  423. $convertedAmount = $plan->getPrice();
  424. if ($currency !== 'EUR') {
  425. try {
  426. $convertedAmount = $currencyChangeService->convertAmount($plan->getPrice(), $currency);
  427. } catch (\Exception $e) {
  428. // If conversion fails, fallback to EUR
  429. $currency = 'EUR';
  430. $convertedAmount = $plan->getPrice();
  431. }
  432. }
  433. try {
  434. $stripeSubscription = $this->subscriptionHelper->chargeCustomer(
  435. $token, $user,
  436. $plan, $quantity,
  437. $stripeCoupon, $schedules,
  438. $currency, $convertedAmount
  439. );
  440. } catch (\Stripe\Exception\CardException $e) {
  441. //error_log("A payment error occurred: {$e->getError()->message}");
  442. $error = 'Il y a un problème pour charger votre carte: '.$e->getError()->message;
  443. } catch (\Stripe\Exception\InvalidRequestException $e) {
  444. $error = 'Il y a un problème pour charger votre carte: '.$e->getError()->message;
  445. } catch (\Exception $e) {
  446. //error_log("Another problem occurred, maybe unrelated to Stripe.");
  447. $error = 'Il y a un problème pour charger votre carte: '.$e;
  448. }
  449. if ( ! $error) {
  450. if ($stripeSubscription->status == 'active') {
  451. $this->addFlash(
  452. 'success',
  453. 'Votre inscription a été reçue. Elle sera validée lorsque le paiement sera confirmé. Vous recevrez un email et vos cours seront visibles dans votre espace membre'
  454. );
  455. //TODO utiliser Messenger
  456. $newSubscriptionOrder = $this->subscriptionHelper->addSubscriptionInBDD(
  457. $user,
  458. $plan,
  459. $schedules,
  460. $oldSubscriptionOrder->getCategory(),
  461. $oldSubscriptionOrder->getTeacher(),
  462. $quantity,
  463. $stripeSubscription,
  464. $subject,
  465. $level,
  466. $participant,
  467. $oldSubscriptionOrder
  468. );
  469. //TODO utiliser Messenger
  470. $this->subscriptionHelper->sendNotificationForNewSubscription($newSubscriptionOrder);
  471. return $this->redirectToRoute(
  472. 'user_courses',
  473. [
  474. 'order' => $oldSubscriptionOrder->getCategory()->getName(),
  475. '_locale' => $request->getLocale(),
  476. ]
  477. );
  478. } else {
  479. $this->addFlash('error', "Votre Paiement n'a pas été accepté par votre banque");
  480. return $this->redirectToRoute(
  481. 'subscription_resubscribe',
  482. [
  483. 'id' => $oldSubscriptionOrder->getId(),
  484. '_locale' => $request->getLocale(),
  485. ]
  486. );
  487. }
  488. } else {
  489. $this->addFlash('error', $error);
  490. return $this->redirectToRoute(
  491. 'subscription_resubscribe',
  492. [
  493. 'id' => $oldSubscriptionOrder->getId(),
  494. '_locale' => $request->getLocale(),
  495. ]
  496. );
  497. }
  498. }
  499. return $this->render(
  500. 'profil/resuscribe.html.twig',
  501. [
  502. 'user' => $user,
  503. 'subscription' => $oldSubscriptionOrder,
  504. 'schedules' => $oldSubscriptionOrder->getSchedules(),
  505. 'errorCard' => $error,
  506. 'stripe_public_key' => $this->getParameter('stripe_public_key'),
  507. 'code' => $code,
  508. 'plans' => $activePlan,
  509. 'currency' => $currency,
  510. ]
  511. );
  512. }
  513. /**
  514. * @Route("/checkout/coupon", name="subscription_checkout_coupon_add", methods={"POST"})
  515. */
  516. public function addCouponAction(Request $request, StripeClient $stripeClient): Response
  517. {
  518. $code = $request->request->get('code');
  519. if ( ! $code) {
  520. return $this->json(['success' => 0, 'message' => 'Vous n\'avez entré aucun coupon']);
  521. }
  522. try {
  523. $stripeCoupon = $stripeClient->findCoupon($code);
  524. } catch (\Stripe\Error\InvalidRequest $e) {
  525. return $this->json(['success' => 0, 'message' => 'Coupon Invalide']);
  526. }
  527. if ($stripeCoupon->amount_off != null) {
  528. return $this->json(['success' => 0, 'message' => 'Coupon invalide pour ces cours']);
  529. }
  530. if ( ! $stripeCoupon->valid) {
  531. return $this->json(['success' => 0, 'message' => 'Coupon expiré']);
  532. }
  533. $request->getSession()->set('code-subscription', $stripeCoupon);
  534. return $this->json(['success' => 1, 'message' => 'Le coupon a été ajouté', 'amount' => $stripeCoupon->percent_off]);
  535. }
  536. //TODO refacto pour code dupliquer à retravailler
  537. /**
  538. * @Route("/{id}/{teacher}/changeteacher", name="subscription_change_teacher")
  539. * @param SubscriptionOrder $subscriptionOrder
  540. * @param Teacher $teacher
  541. * @param TeacherScheduleRepository $teacherScheduleRepository
  542. * @param Request $request
  543. * @param PaidCourseListRepository $paidCourseListRepository
  544. * @param EntityManagerInterface $em
  545. * @param EmailNotification $emailNotification
  546. * @param SlackClient $slackClient
  547. * @param HolidayRepository $holidayRepository
  548. * @param SubscriptionHelper $subscriptionHelper
  549. * @param CourseHelper $courseHelper
  550. *
  551. * @return RedirectResponse|Response
  552. */
  553. public function modifySubscriptionSchedulesAndTeacher(SubscriptionOrder $subscriptionOrder, Teacher $teacher, TeacherScheduleRepository $teacherScheduleRepository,
  554. Request $request,
  555. PaidCourseListRepository $paidCourseListRepository, EntityManagerInterface $em, EmailNotification $emailNotification,
  556. SlackClient $slackClient, HolidayRepository $holidayRepository, SubscriptionHelper $subscriptionHelper,
  557. CourseHelper $courseHelper
  558. ) {
  559. $freeSchedules = $teacherScheduleRepository->findForStudentFreeSchedulesForOneTeacher($teacher);
  560. $oldTeacher = $subscriptionOrder->getTeacher();
  561. if ( ! $this->isGranted('ROLE_SUPPORT')) {
  562. if ($subscriptionHelper->hasCourseToday($subscriptionOrder)) {
  563. $this->addFlash('error', "Vous avez cours aujourd'hui, vous ne pouvez donc pas changer de planning. Merci de modifier votre planning un jour où vous n'avez pas cours");
  564. return $this->redirectToRoute(
  565. 'user_subscription_show',
  566. [
  567. 'id' => $subscriptionOrder->getId(),
  568. '_locale' => $request->getLocale(),
  569. ]
  570. );
  571. }
  572. }
  573. if ($request->isMethod('POST')) {
  574. $holidays = $holidayRepository->findAll();
  575. //Je compte le nb de cours annulé pour les vacances avant le changement de planning
  576. $arrayCanceledCourseForHoliday = $paidCourseListRepository->findNumberCanceledCourseForHolidayAfterScheduleChangeWithQuery($subscriptionOrder)->getQuery()->getArrayResult();;
  577. $nbCanceledCoursesForHoliday = sizeof($arrayCanceledCourseForHoliday);
  578. $schedules = $request->get('newSchedule');
  579. foreach ($schedules as $oldScheduleId => $newScheduleId) {
  580. if ($newScheduleId) {
  581. $oldSchedule = $teacherScheduleRepository->findOneBy(['id' => $oldScheduleId]);
  582. $remainCourses = $paidCourseListRepository->findAllRemainCourseAfterScheduleChange($subscriptionOrder, $oldSchedule);
  583. if ($remainCourses == null) {
  584. $this->addFlash(
  585. 'error',
  586. 'Il ne vous reste plus de nouveaux cours à prendre pour pouvoir changer de professeur. Une fois vos nouveaux cours ajoutés (quand une nouvelle mensualité sera débitée) vous pourrez revenir sur cette page et changer de professeur.'
  587. );
  588. return $this->redirectToRoute(
  589. 'user_subscription_show',
  590. [
  591. 'id' => $subscriptionOrder->getId(),
  592. '_locale' => $request->getLocale(),
  593. ]
  594. );
  595. }
  596. } else {
  597. $this->addFlash('error', 'Merci de choisir un nouveau planning pour remplacer TOUS vos anciens plannings');
  598. return $this->redirectToRoute(
  599. 'subscription_change_teacher',
  600. [
  601. 'id' => $subscriptionOrder->getId(),
  602. 'teacher' => $teacher->getId(),
  603. '_locale' => $request->getLocale(),
  604. ]
  605. );
  606. }
  607. }
  608. foreach ($schedules as $oldScheduleId => $newScheduleId) {
  609. if ($newScheduleId) {
  610. $oldSchedule = $teacherScheduleRepository->findOneBy(['id' => $oldScheduleId]);
  611. $newSchedule = $teacherScheduleRepository->findOneBy(['id' => $newScheduleId]);
  612. //Récupérer tout les cours qui ne seront pas fait :
  613. $remainCourses = $paidCourseListRepository->findAllRemainCourseAfterScheduleChange($subscriptionOrder, $oldSchedule);
  614. //permettre le changement de planning uniquement s'il reste des cours à prendre
  615. if ($remainCourses != null) {
  616. //Récupérer le prochain cours qui aurait du avoir lieu pour en faire la date de libération du oldPlanning (freeDate)
  617. $nextCourse = $paidCourseListRepository->findNextCourseAfterScheduleChange($oldSchedule);
  618. $nextCourseDate = DateTimeImmutable::createFromMutable($nextCourse->getCourseDate());
  619. //Je récupère la date de paiement de ce cours pour le réutiliser pour la nouveau planning, certain abo n'ont pas de paiement date car ce sont des ajout manuel, donc je code en défensive
  620. if ($nextCourse->getPaymentDate() != null) {
  621. $paymentDate = $nextCourse->getPaymentDate();
  622. } else {
  623. $paymentDate = null;
  624. }
  625. //Je récupère le montant du cours pour le réutiliser pour les nouveaux cours de la liste
  626. $priceCourse = $nextCourse->getPrice();
  627. //Libérer l'ancien planning immédiatement
  628. $oldSchedule->setFreeDate($nextCourseDate)
  629. ->setIsFree(true)
  630. ->setSubscriptionOrder(null)
  631. ->setOccupiedUntil(null);
  632. $em->persist($oldSchedule);
  633. //rendre le nouveau planning indisponible si l'abo n'est pas canceled
  634. if ($subscriptionOrder->getIsCancelled() == true) {
  635. $newSchedule->setIsFree(true);
  636. } elseif ($subscriptionOrder->getIsCancelled() == false) {
  637. $newSchedule->setIsFree(false);
  638. } else {
  639. $newSchedule->setIsFree(false);
  640. }
  641. $em->persist($newSchedule);
  642. //ajouter le nouveau planning à l'abonnement
  643. $subscriptionOrder->addSchedule($newSchedule)
  644. ->removeSchedule($oldSchedule);
  645. $em->persist($subscriptionOrder);
  646. //je récupère la date à laquelle le nouveau planning était marqué comme libre, ce sera la date du 1er cours de cette liste
  647. $startNewCourse = DateTimeImmutable::createFromMutable($newSchedule->getFreeDate());
  648. $newFreeDateForNewSchedule = DateTimeImmutable::createFromMutable($newSchedule->getFreeDate());
  649. $nbOfCoursesToAdd = sizeof($remainCourses);
  650. //ajouter les nouveaux cours à la table "PaidCoursesList", j'ajoute le même nombre qui est contenu dans le tableau des cours restant à l'abonnement (remainCourses)
  651. for ($i = 0; $i < $nbOfCoursesToAdd; $i++) {
  652. $newCourseDate = $startNewCourse->modify('+'.$i.'weeks');
  653. $newPaidCourse = $courseHelper->createNewCourse($newCourseDate, $newSchedule, $priceCourse);
  654. foreach ($holidays as $holiday) {
  655. if ($newPaidCourse->getCourseDate() > $holiday->getStartAt() and $newPaidCourse->getCourseDate() < $holiday->getEndAt(
  656. ) and $newPaidCourse->getIsCanceledForHoliday() === null) {
  657. $newPaidCourse->setIsCanceledForHoliday(true);
  658. $em->persist($newPaidCourse);
  659. }
  660. }
  661. if ($newPaidCourse->getIsCanceledForHoliday() != true) {
  662. $newPaidCourse->setIsCanceledForHoliday(false);
  663. $em->persist($newPaidCourse);
  664. }
  665. $newPaidCourse
  666. ->setSubscriptionOrder($subscriptionOrder)
  667. ->setIsForGroup(false)
  668. ->setSessionGroup(null)
  669. ->setPaymentDate($paymentDate);
  670. if ($i == $nbOfCoursesToAdd - 1) {
  671. $nbOfWeekForFreeDate = $nbOfCoursesToAdd + 1;
  672. $newSchedule->setFreeDate($newFreeDateForNewSchedule->modify('+'.$nbOfWeekForFreeDate.'weeks'));
  673. }
  674. $em->persist($newPaidCourse);
  675. $newSchedule->addCourseList($newPaidCourse);
  676. $subscriptionOrder->addCoursesList($newPaidCourse);
  677. $em->persist($newSchedule);
  678. $em->persist($subscriptionOrder);
  679. }
  680. //j'efface les anciens cours du oldPlanning de la table PaidCoursesList
  681. foreach ($remainCourses as $course) {
  682. $em->remove($course);
  683. }
  684. } else {
  685. $this->addFlash(
  686. 'error',
  687. 'Il ne vous reste plus de nouveaux cours à prendre pour pouvoir changer de professeur. Une fois vos nouveaux cours ajoutés (quand une nouvelle mensualité sera débitée) vous pourrez revenir sur cette page et changer de professeur.'
  688. );
  689. return $this->redirectToRoute(
  690. 'user_subscription_show',
  691. [
  692. 'id' => $subscriptionOrder->getId(),
  693. '_locale' => $request->getLocale(),
  694. ]
  695. );
  696. }
  697. } else {
  698. $this->addFlash('error', 'Merci de choisir un nouveau planning pour remplacer TOUS vos anciens plannings');
  699. return $this->redirectToRoute(
  700. 'subscription_change_teacher',
  701. [
  702. 'id' => $subscriptionOrder->getId(),
  703. 'teacher' => $teacher->getId(),
  704. '_locale' => $request->getLocale(),
  705. ]
  706. );
  707. }
  708. }
  709. $subscriptionOrder->setUpdateAt(new \DateTimeImmutable())
  710. ->setTeacher($teacher)
  711. ->setLastScheduleChangeAt(new \DateTime());
  712. $em->persist($subscriptionOrder);
  713. $em->flush();
  714. //Je compte le nb de cours annulé pour les vacances APRES le changement de planning
  715. $newArrayCanceledCourseForHoliday = $paidCourseListRepository->findNumberCanceledCourseForHolidayAfterScheduleChangeWithQuery($subscriptionOrder)->getQuery()->getArrayResult();;
  716. $newNbCanceledCoursesForHoliday = sizeof($newArrayCanceledCourseForHoliday);
  717. if ($nbCanceledCoursesForHoliday != $newNbCanceledCoursesForHoliday) {
  718. $slackClient->notifyAdminNbCanceledCoursesForHolidayDoNotMatchAfterScheduleChange($subscriptionOrder, $nbCanceledCoursesForHoliday, $newNbCanceledCoursesForHoliday);
  719. }
  720. $subscriptionHelper->setLastCourse($subscriptionOrder);
  721. $newTeacherEmail = $teacher->getUserId()->getEmail();
  722. $emailNotification->notifyStudentTeacherChange($subscriptionOrder, $oldTeacher);
  723. $slackClient->newSubscriptionNotifyTeacher($subscriptionOrder);
  724. $slackClient->notifyTeacherStudentChangeTeacher($subscriptionOrder, $oldTeacher);
  725. //$slackClient->notifyAdminStudentChangeTeacher($subscriptionOrder, $oldTeacher);
  726. $emailNotification->registrationSubscriptionNotifyTeacher($subscriptionOrder, $newTeacherEmail);
  727. $this->addFlash('success', 'Professeur modifié avec succes !');
  728. return $this->redirectToRoute(
  729. 'user_subscription_show',
  730. [
  731. 'id' => $subscriptionOrder->getId(),
  732. '_locale' => $request->getLocale(),
  733. ]
  734. );
  735. }
  736. return $this->render(
  737. 'profil/change-teacher.html.twig',
  738. [
  739. 'subscription' => $subscriptionOrder,
  740. 'freeSchedules' => $freeSchedules,
  741. 'teacher' => $teacher,
  742. ]
  743. );
  744. }
  745. //TODO refacto pour code dupliquer à retravailler (spécialement avec la précédente méthode)
  746. /**
  747. * @Route("/{id}/schedule/edit", name="subscription_schedule_edit", methods={"POST"})
  748. * @param SubscriptionOrder $subscriptionOrder
  749. * @param Request $request
  750. * @param PaidCourseListRepository $paidCourseListRepository
  751. * @param TeacherScheduleRepository $teacherScheduleRepository
  752. * @param EntityManagerInterface $em
  753. * @param EmailNotification $emailNotification
  754. * @param SlackClient $slackClient
  755. * @param HolidayRepository $holidayRepository
  756. * @param SubscriptionHelper $subscriptionHelper
  757. * @param CourseHelper $courseHelper
  758. *
  759. * @return RedirectResponse
  760. */
  761. public function changeSubscriptionSchedule(SubscriptionOrder $subscriptionOrder, Request $request, PaidCourseListRepository $paidCourseListRepository,
  762. TeacherScheduleRepository $teacherScheduleRepository, EntityManagerInterface $em, EmailNotification $emailNotification,
  763. SlackClient $slackClient, HolidayRepository $holidayRepository, SubscriptionHelper $subscriptionHelper,
  764. CourseHelper $courseHelper
  765. ): RedirectResponse {
  766. if ($request->isMethod('POST')) {
  767. // Vérification appartenance à l'utilisateur connecté
  768. if ($subscriptionOrder->getUser()->getId() !== $this->getUser()->getId()) {
  769. throw $this->createAccessDeniedException();
  770. }
  771. // Vérification cooldown 30 jours
  772. $lastChange = $subscriptionOrder->getLastScheduleChangeAt();
  773. if ($lastChange !== null) {
  774. $cooldownDate = (clone \DateTime::createFromInterface($lastChange))->modify('+30 days');
  775. if (new \DateTime() < $cooldownDate) {
  776. $this->addFlash('error', 'user.schedule.change.cooldown_error');
  777. return $this->redirectToRoute(
  778. 'user_subscription_show',
  779. ['id' => $subscriptionOrder->getId(), '_locale' => $request->getLocale()]
  780. );
  781. }
  782. }
  783. //Je compte le nb de cours annulé pour les vacances avant le changement de planning
  784. $arrayCanceledCourseForHoliday = $paidCourseListRepository->findNumberCanceledCourseForHolidayAfterScheduleChangeWithQuery($subscriptionOrder)->getQuery()->getArrayResult();;
  785. $nbCanceledCoursesForHoliday = sizeof($arrayCanceledCourseForHoliday);
  786. $holidays = $holidayRepository->findAll();
  787. $schedules = $request->get('newSchedule');
  788. $oldSchedules = [];
  789. $scheduleChangedCount = 0;
  790. $todayDayEn = strtolower(date('l'));
  791. foreach ($schedules as $oldScheduleId => $newScheduleId) {
  792. // 0 ou vide = "garder le planning actuel"
  793. if (!$newScheduleId) {
  794. continue;
  795. }
  796. $oldSchedule = $teacherScheduleRepository->findOneBy(['id' => $oldScheduleId]);
  797. if (!$oldSchedule) {
  798. continue;
  799. }
  800. // Vérification serveur : pas de changement le jour du cours
  801. $scheduleDayEn = strtolower($oldSchedule->getScheduleInstitute()->getDayEn());
  802. if ($scheduleDayEn === $todayDayEn) {
  803. continue;
  804. }
  805. $newSchedule = $teacherScheduleRepository->findOneBy(['id' => $newScheduleId]);
  806. //Récupérer tout les cours qui ne seront pas fait :
  807. $remainCourses = $paidCourseListRepository->findAllRemainCourseAfterScheduleChange($subscriptionOrder, $oldSchedule);
  808. //permettre le changement de planning uniquement s'il reste des cours à prendre
  809. if ($remainCourses != null) {
  810. //Récupérer le prochain cours qui aurait du avoir lieu pour en faire la date de libération du oldPlanning (freeDate)
  811. $nextCourse = $paidCourseListRepository->findNextCourseAfterScheduleChange($oldSchedule);
  812. $nextCourseDate = DateTimeImmutable::createFromMutable($nextCourse->getCourseDate());
  813. //Je récupère la date de paiement de ce cours pour le réutiliser pour la nouveau planning, certain abo n'ont pas de paiement date car ce sont des ajout manuel, donc je code en défensive
  814. if ($nextCourse->getPaymentDate() != null) {
  815. $paymentDate = $nextCourse->getPaymentDate();
  816. } else {
  817. $paymentDate = null;
  818. }
  819. //Je récupère le montant du cours pour le réutiliser pour les nouveaux cours de la liste
  820. $priceCourse = $nextCourse->getPrice();
  821. //Je récup les anciens plannings dans un tableau en dehors de la boucle pour pouvoir les envoyer par mail à l'élève.
  822. $oldSchedules[] = $oldSchedule;
  823. //Libérer l'ancien planning immédiatement
  824. $oldSchedule->setFreeDate($nextCourseDate)
  825. ->setIsFree(true)
  826. ->setSubscriptionOrder(null)
  827. ->setOccupiedUntil(null);
  828. $em->persist($oldSchedule);
  829. //rendre le nouveau planning indisponible si l'abo n'est pas canceled
  830. if ($subscriptionOrder->getIsCancelled() == true) {
  831. $newSchedule->setIsFree(true);
  832. } elseif ($subscriptionOrder->getIsCancelled() == false) {
  833. $newSchedule->setIsFree(false);
  834. } else {
  835. $newSchedule->setIsFree(false);
  836. }
  837. $em->persist($newSchedule);
  838. //ajouter le nouveau planning à l'abonnement
  839. $subscriptionOrder->addSchedule($newSchedule)
  840. ->removeSchedule($oldSchedule);
  841. $em->persist($subscriptionOrder);
  842. //je récupère la date à laquelle le nouveau planning était marqué comme libre, ce sera la date du 1er cours de cette liste
  843. $startNewCourse = DateTimeImmutable::createFromMutable($newSchedule->getFreeDate());
  844. $newFreeDateForNewSchedule = DateTimeImmutable::createFromMutable($newSchedule->getFreeDate());
  845. $nbOfCoursesToAdd = sizeof($remainCourses);
  846. //ajouter les nouveaux cours à la table "PaidCoursesList", j'ajoute le même nombre qui est contenu dans le tableau des cours restant à l'abonnement (remainCourses)
  847. for ($i = 0; $i < $nbOfCoursesToAdd; $i++) {
  848. $newCourseDate = $startNewCourse->modify('+'.$i.'weeks');
  849. $newPaidCourse = $courseHelper->createNewCourse($newCourseDate, $newSchedule, $priceCourse);
  850. foreach ($holidays as $holiday) {
  851. if ($newPaidCourse->getCourseDate() > $holiday->getStartAt() and $newPaidCourse->getCourseDate() < $holiday->getEndAt(
  852. ) and $newPaidCourse->getIsCanceledForHoliday() === null) {
  853. $newPaidCourse->setIsCanceledForHoliday(true);
  854. $em->persist($newPaidCourse);
  855. }
  856. }
  857. if ($newPaidCourse->getIsCanceledForHoliday() != true) {
  858. $newPaidCourse->setIsCanceledForHoliday(false);
  859. $em->persist($newPaidCourse);
  860. }
  861. $newPaidCourse
  862. ->setSubscriptionOrder($subscriptionOrder)
  863. ->setIsForGroup(false)
  864. ->setSessionGroup(null)
  865. ->setPaymentDate($paymentDate);
  866. if ($i == $nbOfCoursesToAdd - 1) {
  867. $nbOfWeekForFreeDate = $nbOfCoursesToAdd + 1;
  868. $newSchedule->setFreeDate($newFreeDateForNewSchedule->modify('+'.$nbOfWeekForFreeDate.'weeks'));
  869. }
  870. $em->persist($newPaidCourse);
  871. $newSchedule->addCourseList($newPaidCourse);
  872. $subscriptionOrder->addCoursesList($newPaidCourse);
  873. $em->persist($newSchedule);
  874. $em->persist($subscriptionOrder);
  875. }
  876. //j'efface les anciens cours du oldPlanning de la table PaidCoursesList
  877. foreach ($remainCourses as $course) {
  878. $em->remove($course);
  879. }
  880. $scheduleChangedCount++;
  881. } else {
  882. $this->addFlash('error', 'Il ne vous reste plus de nouveaux cours à prendre pour pouvoir changer de planning.');
  883. return $this->redirectToRoute(
  884. 'user_subscription_show',
  885. [
  886. 'id' => $subscriptionOrder->getId(),
  887. '_locale' => $request->getLocale(),
  888. ]
  889. );
  890. }
  891. }
  892. $subscriptionOrder->setUpdateAt(new \DateTimeImmutable());
  893. if ($scheduleChangedCount > 0) {
  894. $subscriptionOrder->setLastScheduleChangeAt(new \DateTime());
  895. }
  896. $em->persist($subscriptionOrder);
  897. $em->flush();
  898. //Je compte le nb de cours annulé pour les vacances APRES le changement de planning
  899. $newArrayCanceledCourseForHoliday = $paidCourseListRepository->findNumberCanceledCourseForHolidayAfterScheduleChangeWithQuery($subscriptionOrder)->getQuery()->getArrayResult();;
  900. $newNbCanceledCoursesForHoliday = sizeof($newArrayCanceledCourseForHoliday);
  901. if ($nbCanceledCoursesForHoliday != $newNbCanceledCoursesForHoliday) {
  902. $slackClient->notifyAdminNbCanceledCoursesForHolidayDoNotMatchAfterScheduleChange($subscriptionOrder, $nbCanceledCoursesForHoliday, $newNbCanceledCoursesForHoliday);
  903. }
  904. $subscriptionHelper->setLastCourse($subscriptionOrder);
  905. $emailNotification->notifyStudentSubscriptionSchedulesChange($subscriptionOrder, $oldSchedules);
  906. $emailNotification->notifyTeacherSubscriptionScheduleChange($subscriptionOrder, $oldSchedules);
  907. $slackClient->notifyTeacherChangeScheduleSubscription($subscriptionOrder);
  908. $this->addFlash('success', 'Planning modifié avec succes !');
  909. return $this->redirectToRoute(
  910. 'user_subscription_show',
  911. [
  912. 'id' => $subscriptionOrder->getId(),
  913. '_locale' => $request->getLocale(),
  914. ]
  915. );
  916. }
  917. }
  918. }