<?php
namespace App\V4\Form\Type\Quote;
use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface;
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
use ApiPlatform\Core\Exception\ResourceClassNotSupportedException;
use App\Form\Type\AbstractViewOrderAwareType;
use App\Form\Type\CustomerResource\CustomerFileType;
use App\Form\Type\SubresourceChoicesTrait;
use App\Listing\Transformer\ListingResponseTransformer;
use App\Model\Traits\TimeStampableFormType;
use App\Model\ViewOrder\ViewOrder;
use App\Normalizer\Form\TextAreaTypeNormalizer;
use App\Security\SecurityConfig;
use App\Security\User;
use App\Service\Cache\CacheManager;
use App\V4\EventSubscriber\Quote\QuotePersistEventSubscriber;
use App\V4\Form\Type\ContactConcernedFilterTrait;
use App\V4\Form\Type\CustomerEventTriggerAwareFormTypeTrait;
use App\V4\Form\Type\ManagedByFilterTrait;
use App\V4\Form\Type\QuoteLine\QuoteLineType;
use App\V4\Form\Type\SectionNameFilterTrait;
use App\V4\Logger\SentryLogger;
use App\V4\Model\Prospect\Prospect;
use App\V4\Model\Quote\Quote;
use App\V4\Model\QuoteReason\QuoteReason;
use App\V4\Model\QuoteState\QuoteState;
use DateTime;
use Psr\Cache\CacheException;
use Psr\Cache\InvalidArgumentException;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Validator\Constraints\Valid;
class QuoteType extends AbstractViewOrderAwareType
{
use ContactConcernedFilterTrait;
use ManagedByFilterTrait;
use SectionNameFilterTrait;
use TimeStampableFormType;
use CustomerEventTriggerAwareFormTypeTrait;
use SubresourceChoicesTrait {
getEntityChoices as private getEntityChoicesTrait;
}
/**
* @var CollectionDataProviderInterface
*/
private $collectionDataProvider;
/**
* @var CacheManager
*/
private $cacheManager;
/**
* @var Security
*/
private $security;
/**
* @var ItemDataProviderInterface
*/
private $itemDataProvider;
/**
* @var QuotePersistEventSubscriber
*/
private $quotePersistEventSubscriber;
/**
* @var SentryLogger
*/
private $sentryLogger;
public function __construct(
CollectionDataProviderInterface $collectionDataProvider,
CacheManager $cacheManager,
Security $security,
ItemDataProviderInterface $itemDataProvider,
QuotePersistEventSubscriber $quotePersistEventSubscriber,
SentryLogger $sentryLogger
) {
$this->collectionDataProvider = $collectionDataProvider;
$this->cacheManager = $cacheManager;
$this->security = $security;
$this->itemDataProvider = $itemDataProvider;
$this->quotePersistEventSubscriber = $quotePersistEventSubscriber;
$this->sentryLogger = $sentryLogger;
}
/**
* @param FormBuilderInterface $builder
* @param array $options
*
* @return void
*
* @throws CacheException
* @throws InvalidArgumentException
* @throws ResourceClassNotSupportedException
*/
public function buildForm(FormBuilderInterface $builder, array $options): void
{
parent::buildForm($builder, $options);
$prospectId = $options['prospectId'];
$quote = $builder->getData();
if ($quote instanceof Quote) {
$prospectId = $quote->getProspectId() ?? $prospectId;
$prospect = $quote->getProspect();
if ($prospect instanceof Prospect) {
$prospectId = $prospect->getId();
}
}
/** @var User $user */
$user = $this->security->getUser();
$builder
->add('prospectId', TextType::class, [
'data' => $prospectId,
'required' => false,
'attr' => ['type' => 'hidden'],
])
->add('name', TextType::class, [
'label' => 'quote_title',
'required' => true,
])
->add('potential', IntegerType::class, [
'label' => 'quote_potential',
'attr' => ['min' => 0, 'max' => 100, 'step' => 25, 'type' => 'range'],
'required' => false,
])
->add('issuedAt', DateType::class, [
'input' => 'datetime',
'widget' => 'single_text',
'html5' => false,
'format' => 'yyyy-MM-dd',
'data' => $quote instanceof Quote && $quote->getIssuedAt() instanceof DateTime ? $quote->getIssuedAt() : new DateTime(),
'required' => false,
])
->add('warrantlyAt', DateType::class, [
'input' => 'datetime',
'widget' => 'single_text',
'html5' => false,
'format' => 'yyyy-MM-dd',
'required' => false,
])
->add('expiredAt', DateType::class, [
'input' => 'datetime',
'widget' => 'single_text',
'html5' => false,
'format' => 'yyyy-MM-dd',
'required' => false,
])
->add('managedBy', ChoiceType::class, [
'choices' => $this->getManagedByChoices(),
'required' => true,
'data' => $quote instanceof Quote && $quote->getManagedBy() ? $quote->getManagedBy() : $user->getUserId(),
])
->add('quoteNumber', TextType::class, [
'required' => false,
])
->add('contractNumber', TextType::class, [
'required' => false,
])
->add('description', TextareaType::class, [
'required' => false,
'attr' => [
TextAreaTypeNormalizer::DATA_ATTR_WYSIWYG_MODE => TextAreaTypeNormalizer::WYSIWYG_MODE_FULL,
],
])
->add('comment', TextareaType::class, [
'required' => false,
'attr' => [
TextAreaTypeNormalizer::DATA_ATTR_WYSIWYG_MODE => TextAreaTypeNormalizer::WYSIWYG_MODE_FULL,
],
])
->add('terms', TextareaType::class, [
'required' => false,
'attr' => [
TextAreaTypeNormalizer::DATA_ATTR_WYSIWYG_MODE => TextAreaTypeNormalizer::WYSIWYG_MODE_FULL,
],
])
->add('isSalesForecast', CheckboxType::class, [
'false_values' => [null, '0', 0, false, '', 'false'],
'required' => false,
])
->add('isMaintenance', CheckboxType::class, [
'false_values' => [null, '0', 0, false, '', 'false'],
'required' => false,
])
->add('status', ChoiceType::class, [
'choices' => $this->getEntityChoices(QuoteState::class),
'choice_label' => 'name',
'choice_value' => 'id',
'attr' => [
ListingResponseTransformer::FORM_SORT_KEY => 'status.name',
],
])
->add('totalExcludingVat', NumberType::class, [
'required' => false,
'scale' => 2,
])
->add('contactId', ChoiceType::class, [
'choices' => null !== $prospectId
? $this->getContactConcernedIdChoices($this->collectionDataProvider, $prospectId)
: [],
'required' => false,
])
->add('expectedSignedAt', DateType::class, [
'input' => 'datetime',
'widget' => 'single_text',
'html5' => false,
'format' => 'yyyy-MM-dd',
'required' => false,
])
//Sert uniquement pour le listing (affichage dans les viewOrders à droite d'une liste
->add('weightedTotal', HiddenType::class, [
'required' => false,
])
->add('customerFiles', CollectionType::class, [
'entry_type' => CustomerFileType::class,
'entry_options' => [
'allow_extra_fields' => $options['allow_extra_fields'] ?? false,
],
'required' => false,
'allow_add' => true,
'delete_empty' => true,
])
->add('sectionName', ChoiceType::class, [
'choices' => $this->getSectionNameChoices(),
'required' => false,
'attr' => [
'data-hide-from-vieworders' => true,
],
])
->add('tasks', CollectionType::class, [
'entry_type' => QuoteTaskExternalRefType::class,
'entry_options' => [
'allow_extra_fields' => $options['allow_extra_fields'] ?? false,
],
'required' => false,
'allow_add' => true,
'delete_empty' => true,
'attr' => [
'data-hide-from-vieworders' => true,
'type' => 'hidden',
],
])
->add('status_isWon', HiddenType::class, [
'label' => 'status_isWon',
'required' => false,
'mapped' => false,
'attr' => [
ListingResponseTransformer::FORM_SORT_KEY => 'status.isWon',
],
])
->add('reason', ChoiceType::class, [
// @todo : vérifier la logique ne renvoyer aucun choices pour la fonctionnalité souhaité
// @todo : qui est que seul les choix liés à un statut s'affiche (PV-1020)
'choices' => $this->getEntityChoices(QuoteReason::class),
'choice_label' => 'name',
'choice_value' => 'id',
'required' => false,
'attr' => [
'dependsOnField' => [
'fieldKey' => 'status',
'choices' => $this->getQuoteReasonsByQuoteState(),
],
],
])
->add('reasonComment', TextareaType::class, [
'required' => false,
'attr' => [
TextAreaTypeNormalizer::DATA_ATTR_WYSIWYG_MODE => TextAreaTypeNormalizer::WYSIWYG_MODE_FULL,
],
])
->add('externalId', HiddenType::class, [
'required' => false,
])
->add('newPdf', HiddenType::class, [
'mapped' => false,
'required' => false,
'attr' => [
'data-hide-from-vieworders' => true,
],
])
;
if ($this->security->isGranted(SecurityConfig::MODULE_QUOTE_LINE)) {
$builder
->add('quoteLines', CollectionType::class, [
'label' => 'quote_lines',
'entry_type' => QuoteLineType::class,
'entry_options' => [
'allow_extra_fields' => $options['allow_extra_fields'] ?? false,
],
'prototype' => true,
'prototype_name' => 'quote_lines__name__',
'allow_add' => true,
'allow_delete' => true,
'by_reference' => true,
'required' => false,
'constraints' => [new Valid()],
'attr' => [
'data-hide-from-vieworders' => true,
],
])
;
}
$builder->addEventSubscriber($this->quotePersistEventSubscriber);
$builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
/** @var Quote $quote */
$quote = $event->getData();
foreach ($quote->getQuoteLines() as $i => $quoteLine) {
$quoteLine->setPosition($i);
}
});
if (null === $quote || ($quote instanceof Quote && !$quote->isSplit())) {
$builder
->addEventSubscriber($this->customEventTriggerPreSubmitSubscriber)
->addEventSubscriber($this->customEventTriggerPostSetDataOnCreationSubscriber)
;
}
}
/**
* @param OptionsResolver $resolver
*
* @return void
*/
public function configureOptions(OptionsResolver $resolver): void
{
parent::configureOptions($resolver);
$resolver->setDefaults([
'data_class' => Quote::class,
'csrf_protection' => false,
'prospectId' => null,
self::FORM_CONFIG_VIEW_ORDER_ENTITY => Quote::class,
self::FORM_CONFIG_VIEW_ORDER_TYPE => ViewOrder::VIEWORDER_TYPE_FORM,
self::FORM_CONFIG_SPECIFIC_FIELD_ENTITY => Quote::class,
self::FORM_CONFIG_VIEW_ORDER_ENTITY_TYPE => null,
]);
}
/**
* @param string $entityFQCN
* @param array $filters
*
* @return array<string, object>
*
* @throws CacheException
* @throws InvalidArgumentException
*/
private function getEntityChoices(string $entityFQCN, array $filters = []): array
{
return $this->getEntityChoicesTrait(
$this->collectionDataProvider,
$this->cacheManager,
$this->sentryLogger,
$entityFQCN,
$filters
);
}
/**
* @return array
*
* @throws ResourceClassNotSupportedException
*/
private function getQuoteReasonsByQuoteState(): array
{
$quoteStates = $this->collectionDataProvider->getCollection(QuoteState::class);
$quoteReasonsByQuoteStates = [];
foreach ($quoteStates as $quoteState) {
$quoteReasonsByQuoteStates[$quoteState->getId()] = [];
foreach ($quoteState->getReasons() as $reason) {
$quoteReasonsByQuoteStates[$quoteState->getId()][] = [
'label' => $reason->getName(),
'value' => $reason->getId(),
];
}
}
return $quoteReasonsByQuoteStates;
}
}