src/V4/Form/Type/Quote/QuoteType.php line 48

Open in your IDE?
  1. <?php
  2. namespace App\V4\Form\Type\Quote;
  3. use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface;
  4. use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
  5. use ApiPlatform\Core\Exception\ResourceClassNotSupportedException;
  6. use App\Form\Type\AbstractViewOrderAwareType;
  7. use App\Form\Type\CustomerResource\CustomerFileType;
  8. use App\Form\Type\SubresourceChoicesTrait;
  9. use App\Listing\Transformer\ListingResponseTransformer;
  10. use App\Model\Traits\TimeStampableFormType;
  11. use App\Model\ViewOrder\ViewOrder;
  12. use App\Normalizer\Form\TextAreaTypeNormalizer;
  13. use App\Security\SecurityConfig;
  14. use App\Security\User;
  15. use App\Service\Cache\CacheManager;
  16. use App\V4\EventSubscriber\Quote\QuotePersistEventSubscriber;
  17. use App\V4\Form\Type\ContactConcernedFilterTrait;
  18. use App\V4\Form\Type\CustomerEventTriggerAwareFormTypeTrait;
  19. use App\V4\Form\Type\ManagedByFilterTrait;
  20. use App\V4\Form\Type\QuoteLine\QuoteLineType;
  21. use App\V4\Form\Type\SectionNameFilterTrait;
  22. use App\V4\Logger\SentryLogger;
  23. use App\V4\Model\Prospect\Prospect;
  24. use App\V4\Model\Quote\Quote;
  25. use App\V4\Model\QuoteReason\QuoteReason;
  26. use App\V4\Model\QuoteState\QuoteState;
  27. use DateTime;
  28. use Psr\Cache\CacheException;
  29. use Psr\Cache\InvalidArgumentException;
  30. use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
  31. use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
  32. use Symfony\Component\Form\Extension\Core\Type\CollectionType;
  33. use Symfony\Component\Form\Extension\Core\Type\DateType;
  34. use Symfony\Component\Form\Extension\Core\Type\HiddenType;
  35. use Symfony\Component\Form\Extension\Core\Type\IntegerType;
  36. use Symfony\Component\Form\Extension\Core\Type\NumberType;
  37. use Symfony\Component\Form\Extension\Core\Type\TextareaType;
  38. use Symfony\Component\Form\Extension\Core\Type\TextType;
  39. use Symfony\Component\Form\FormBuilderInterface;
  40. use Symfony\Component\Form\FormEvent;
  41. use Symfony\Component\Form\FormEvents;
  42. use Symfony\Component\OptionsResolver\OptionsResolver;
  43. use Symfony\Component\Security\Core\Security;
  44. use Symfony\Component\Validator\Constraints\Valid;
  45. class QuoteType extends AbstractViewOrderAwareType
  46. {
  47.     use ContactConcernedFilterTrait;
  48.     use ManagedByFilterTrait;
  49.     use SectionNameFilterTrait;
  50.     use TimeStampableFormType;
  51.     use CustomerEventTriggerAwareFormTypeTrait;
  52.     use SubresourceChoicesTrait {
  53.         getEntityChoices as private getEntityChoicesTrait;
  54.     }
  55.     /**
  56.      * @var CollectionDataProviderInterface
  57.      */
  58.     private $collectionDataProvider;
  59.     /**
  60.      * @var CacheManager
  61.      */
  62.     private $cacheManager;
  63.     /**
  64.      * @var Security
  65.      */
  66.     private $security;
  67.     /**
  68.      * @var ItemDataProviderInterface
  69.      */
  70.     private $itemDataProvider;
  71.     /**
  72.      * @var QuotePersistEventSubscriber
  73.      */
  74.     private $quotePersistEventSubscriber;
  75.     /**
  76.      * @var SentryLogger
  77.      */
  78.     private $sentryLogger;
  79.     public function __construct(
  80.         CollectionDataProviderInterface $collectionDataProvider,
  81.         CacheManager $cacheManager,
  82.         Security $security,
  83.         ItemDataProviderInterface $itemDataProvider,
  84.         QuotePersistEventSubscriber $quotePersistEventSubscriber,
  85.         SentryLogger $sentryLogger
  86.     ) {
  87.         $this->collectionDataProvider $collectionDataProvider;
  88.         $this->cacheManager $cacheManager;
  89.         $this->security $security;
  90.         $this->itemDataProvider $itemDataProvider;
  91.         $this->quotePersistEventSubscriber $quotePersistEventSubscriber;
  92.         $this->sentryLogger $sentryLogger;
  93.     }
  94.     /**
  95.      * @param FormBuilderInterface $builder
  96.      * @param array                $options
  97.      *
  98.      * @return void
  99.      *
  100.      * @throws CacheException
  101.      * @throws InvalidArgumentException
  102.      * @throws ResourceClassNotSupportedException
  103.      */
  104.     public function buildForm(FormBuilderInterface $builder, array $options): void
  105.     {
  106.         parent::buildForm($builder$options);
  107.         $prospectId $options['prospectId'];
  108.         $quote $builder->getData();
  109.         if ($quote instanceof Quote) {
  110.             $prospectId $quote->getProspectId() ?? $prospectId;
  111.             $prospect $quote->getProspect();
  112.             if ($prospect instanceof Prospect) {
  113.                 $prospectId $prospect->getId();
  114.             }
  115.         }
  116.         /** @var User $user */
  117.         $user $this->security->getUser();
  118.         $builder
  119.             ->add('prospectId'TextType::class, [
  120.                 'data' => $prospectId,
  121.                 'required' => false,
  122.                 'attr' => ['type' => 'hidden'],
  123.             ])
  124.             ->add('name'TextType::class, [
  125.                 'label' => 'quote_title',
  126.                 'required' => true,
  127.             ])
  128.             ->add('potential'IntegerType::class, [
  129.                 'label' => 'quote_potential',
  130.                 'attr' => ['min' => 0'max' => 100'step' => 25'type' => 'range'],
  131.                 'required' => false,
  132.             ])
  133.             ->add('issuedAt'DateType::class, [
  134.                 'input' => 'datetime',
  135.                 'widget' => 'single_text',
  136.                 'html5' => false,
  137.                 'format' => 'yyyy-MM-dd',
  138.                 'data' => $quote instanceof Quote && $quote->getIssuedAt() instanceof DateTime $quote->getIssuedAt() : new DateTime(),
  139.                 'required' => false,
  140.             ])
  141.             ->add('warrantlyAt'DateType::class, [
  142.                 'input' => 'datetime',
  143.                 'widget' => 'single_text',
  144.                 'html5' => false,
  145.                 'format' => 'yyyy-MM-dd',
  146.                 'required' => false,
  147.             ])
  148.             ->add('expiredAt'DateType::class, [
  149.                 'input' => 'datetime',
  150.                 'widget' => 'single_text',
  151.                 'html5' => false,
  152.                 'format' => 'yyyy-MM-dd',
  153.                 'required' => false,
  154.             ])
  155.             ->add('managedBy'ChoiceType::class, [
  156.                 'choices' => $this->getManagedByChoices(),
  157.                 'required' => true,
  158.                 'data' => $quote instanceof Quote && $quote->getManagedBy() ? $quote->getManagedBy() : $user->getUserId(),
  159.             ])
  160.             ->add('quoteNumber'TextType::class, [
  161.                 'required' => false,
  162.             ])
  163.             ->add('contractNumber'TextType::class, [
  164.                 'required' => false,
  165.             ])
  166.             ->add('description'TextareaType::class, [
  167.                 'required' => false,
  168.                 'attr' => [
  169.                     TextAreaTypeNormalizer::DATA_ATTR_WYSIWYG_MODE => TextAreaTypeNormalizer::WYSIWYG_MODE_FULL,
  170.                 ],
  171.             ])
  172.             ->add('comment'TextareaType::class, [
  173.                 'required' => false,
  174.                 'attr' => [
  175.                     TextAreaTypeNormalizer::DATA_ATTR_WYSIWYG_MODE => TextAreaTypeNormalizer::WYSIWYG_MODE_FULL,
  176.                 ],
  177.             ])
  178.             ->add('terms'TextareaType::class, [
  179.                 'required' => false,
  180.                 'attr' => [
  181.                     TextAreaTypeNormalizer::DATA_ATTR_WYSIWYG_MODE => TextAreaTypeNormalizer::WYSIWYG_MODE_FULL,
  182.                 ],
  183.             ])
  184.             ->add('isSalesForecast'CheckboxType::class, [
  185.                 'false_values' => [null'0'0false'''false'],
  186.                 'required' => false,
  187.             ])
  188.             ->add('isMaintenance'CheckboxType::class, [
  189.                 'false_values' => [null'0'0false'''false'],
  190.                 'required' => false,
  191.             ])
  192.             ->add('status'ChoiceType::class, [
  193.                 'choices' => $this->getEntityChoices(QuoteState::class),
  194.                 'choice_label' => 'name',
  195.                 'choice_value' => 'id',
  196.                 'attr' => [
  197.                     ListingResponseTransformer::FORM_SORT_KEY => 'status.name',
  198.                 ],
  199.             ])
  200.             ->add('totalExcludingVat'NumberType::class, [
  201.                 'required' => false,
  202.                 'scale' => 2,
  203.             ])
  204.             ->add('contactId'ChoiceType::class, [
  205.                 'choices' => null !== $prospectId
  206.                     $this->getContactConcernedIdChoices($this->collectionDataProvider$prospectId)
  207.                     : [],
  208.                 'required' => false,
  209.             ])
  210.             ->add('expectedSignedAt'DateType::class, [
  211.                 'input' => 'datetime',
  212.                 'widget' => 'single_text',
  213.                 'html5' => false,
  214.                 'format' => 'yyyy-MM-dd',
  215.                 'required' => false,
  216.             ])
  217.             //Sert uniquement pour le listing (affichage dans les viewOrders à droite d'une liste
  218.             ->add('weightedTotal'HiddenType::class, [
  219.                 'required' => false,
  220.             ])
  221.             ->add('customerFiles'CollectionType::class, [
  222.                 'entry_type' => CustomerFileType::class,
  223.                 'entry_options' => [
  224.                     'allow_extra_fields' => $options['allow_extra_fields'] ?? false,
  225.                 ],
  226.                 'required' => false,
  227.                 'allow_add' => true,
  228.                 'delete_empty' => true,
  229.             ])
  230.             ->add('sectionName'ChoiceType::class, [
  231.                 'choices' => $this->getSectionNameChoices(),
  232.                 'required' => false,
  233.                 'attr' => [
  234.                     'data-hide-from-vieworders' => true,
  235.                 ],
  236.             ])
  237.             ->add('tasks'CollectionType::class, [
  238.                 'entry_type' => QuoteTaskExternalRefType::class,
  239.                 'entry_options' => [
  240.                     'allow_extra_fields' => $options['allow_extra_fields'] ?? false,
  241.                 ],
  242.                 'required' => false,
  243.                 'allow_add' => true,
  244.                 'delete_empty' => true,
  245.                 'attr' => [
  246.                     'data-hide-from-vieworders' => true,
  247.                     'type' => 'hidden',
  248.                 ],
  249.             ])
  250.             ->add('status_isWon'HiddenType::class, [
  251.                 'label' => 'status_isWon',
  252.                 'required' => false,
  253.                 'mapped' => false,
  254.                 'attr' => [
  255.                     ListingResponseTransformer::FORM_SORT_KEY => 'status.isWon',
  256.                 ],
  257.             ])
  258.             ->add('reason'ChoiceType::class, [
  259.                 // @todo : vérifier la logique ne renvoyer aucun choices pour la fonctionnalité souhaité
  260.                 // @todo : qui est que seul les choix liés à un statut s'affiche (PV-1020)
  261.                 'choices' => $this->getEntityChoices(QuoteReason::class),
  262.                 'choice_label' => 'name',
  263.                 'choice_value' => 'id',
  264.                 'required' => false,
  265.                 'attr' => [
  266.                     'dependsOnField' => [
  267.                         'fieldKey' => 'status',
  268.                         'choices' => $this->getQuoteReasonsByQuoteState(),
  269.                     ],
  270.                 ],
  271.             ])
  272.             ->add('reasonComment'TextareaType::class, [
  273.                 'required' => false,
  274.                 'attr' => [
  275.                     TextAreaTypeNormalizer::DATA_ATTR_WYSIWYG_MODE => TextAreaTypeNormalizer::WYSIWYG_MODE_FULL,
  276.                 ],
  277.             ])
  278.             ->add('externalId'HiddenType::class, [
  279.                 'required' => false,
  280.             ])
  281.             ->add('newPdf'HiddenType::class, [
  282.                 'mapped' => false,
  283.                 'required' => false,
  284.                 'attr' => [
  285.                     'data-hide-from-vieworders' => true,
  286.                 ],
  287.             ])
  288.         ;
  289.         if ($this->security->isGranted(SecurityConfig::MODULE_QUOTE_LINE)) {
  290.             $builder
  291.                 ->add('quoteLines'CollectionType::class, [
  292.                     'label' => 'quote_lines',
  293.                     'entry_type' => QuoteLineType::class,
  294.                     'entry_options' => [
  295.                         'allow_extra_fields' => $options['allow_extra_fields'] ?? false,
  296.                     ],
  297.                     'prototype' => true,
  298.                     'prototype_name' => 'quote_lines__name__',
  299.                     'allow_add' => true,
  300.                     'allow_delete' => true,
  301.                     'by_reference' => true,
  302.                     'required' => false,
  303.                     'constraints' => [new Valid()],
  304.                     'attr' => [
  305.                         'data-hide-from-vieworders' => true,
  306.                     ],
  307.                 ])
  308.             ;
  309.         }
  310.         $builder->addEventSubscriber($this->quotePersistEventSubscriber);
  311.         $builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
  312.             /** @var Quote $quote */
  313.             $quote $event->getData();
  314.             foreach ($quote->getQuoteLines() as $i => $quoteLine) {
  315.                 $quoteLine->setPosition($i);
  316.             }
  317.         });
  318.         if (null === $quote || ($quote instanceof Quote && !$quote->isSplit())) {
  319.             $builder
  320.                 ->addEventSubscriber($this->customEventTriggerPreSubmitSubscriber)
  321.                 ->addEventSubscriber($this->customEventTriggerPostSetDataOnCreationSubscriber)
  322.             ;
  323.         }
  324.     }
  325.     /**
  326.      * @param OptionsResolver $resolver
  327.      *
  328.      * @return void
  329.      */
  330.     public function configureOptions(OptionsResolver $resolver): void
  331.     {
  332.         parent::configureOptions($resolver);
  333.         $resolver->setDefaults([
  334.             'data_class' => Quote::class,
  335.             'csrf_protection' => false,
  336.             'prospectId' => null,
  337.             self::FORM_CONFIG_VIEW_ORDER_ENTITY => Quote::class,
  338.             self::FORM_CONFIG_VIEW_ORDER_TYPE => ViewOrder::VIEWORDER_TYPE_FORM,
  339.             self::FORM_CONFIG_SPECIFIC_FIELD_ENTITY => Quote::class,
  340.             self::FORM_CONFIG_VIEW_ORDER_ENTITY_TYPE => null,
  341.         ]);
  342.     }
  343.     /**
  344.      * @param string $entityFQCN
  345.      * @param array  $filters
  346.      *
  347.      * @return array<string, object>
  348.      *
  349.      * @throws CacheException
  350.      * @throws InvalidArgumentException
  351.      */
  352.     private function getEntityChoices(string $entityFQCN, array $filters = []): array
  353.     {
  354.         return $this->getEntityChoicesTrait(
  355.             $this->collectionDataProvider,
  356.             $this->cacheManager,
  357.             $this->sentryLogger,
  358.             $entityFQCN,
  359.             $filters
  360.         );
  361.     }
  362.     /**
  363.      * @return array
  364.      *
  365.      * @throws ResourceClassNotSupportedException
  366.      */
  367.     private function getQuoteReasonsByQuoteState(): array
  368.     {
  369.         $quoteStates $this->collectionDataProvider->getCollection(QuoteState::class);
  370.         $quoteReasonsByQuoteStates = [];
  371.         foreach ($quoteStates as $quoteState) {
  372.             $quoteReasonsByQuoteStates[$quoteState->getId()] = [];
  373.             foreach ($quoteState->getReasons() as $reason) {
  374.                 $quoteReasonsByQuoteStates[$quoteState->getId()][] = [
  375.                     'label' => $reason->getName(),
  376.                     'value' => $reason->getId(),
  377.                 ];
  378.             }
  379.         }
  380.         return $quoteReasonsByQuoteStates;
  381.     }
  382. }