Hola. Tengo lo siguiente.
En mi aplicación debo crear órdenes de compra. Al crearse la orden de compra, está tiene una relación de ManyToOne con un listado de proveedores, relación que no debe poder modificarse una vez creada la entidad ya que cada proveedor tiene sus propios productos y no se permiten más de dos proveedores.
OrdenCompra
class OrdenesCompra { // ... /** * @ORM\ManyToOne(targetEntity="Proveedores", inversedBy="ordenes") * @ORM\JoinColumn(name="proveedor_id", referencedColumnName="id") */ protected $proveedores; }
Proveedores
class Proveedores { /** * @ORM\OneToMany(targetEntity="OrdenesCompra", mappedBy="proveedores") */ private $ordenes; /** * @ORM\OneToMany(targetEntity="Productos", mappedBy="proveedores") */ protected $productos; }
Productos
class Productos { /** * @ORM\ManyToOne(targetEntity="Proveedores", inversedBy="productos") * @ORM\JoinColumn(name="proveedor_id", referencedColumnName="id") */ private $proveedores; }
La clase del formulario de OrdenCompra:
use [bundle]\[bundle]Bundle\Form\EventListener\OrdenesCompraSubscriber; class OrdenesCompraType extends AbstractType { /** * @param FormBuilderInterface $builder * @param array $options */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder->addEventSubscriber(new OrdenesCompraSubscriber()); } /** * @param OptionsResolverInterface $resolver */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'data_class' => '[Bundle]\[Bundle]Bundle\Entity\OrdenesCompra', // 'cascade_validation' => true )); } /** * @return string */ public function getName() { return 'orden_compra'; } }
class OrdenesCompraSubscriber implements EventSubscriberInterface { public static function getSubscribedEvents() { return array( FormEvents::PRE_SET_DATA => 'preSetData' ); } public function preSetData(FormEvent $event) { $data = $event->getData(); $form = $event->getForm(); if (null === $data) { return; } if (!$data->getId()) { $form->add('numerofactura', 'text', array( 'label' => 'Número de factura' )); $form->add('proveedores', 'entity', array( 'class' => 'ProveedoresBundle:Proveedores', 'property' => 'proveedor', 'empty_value'=> 'Seleccione', 'label' => 'Proveedor' )); } else { $form ->add('ordenes', 'collection', array('type' => new OrdenesDetalleType(), 'label' => false, 'by_reference' => false, 'allow_delete' => true, 'allow_add' => true, 'prototype' => true, )); } } }
OrdenesDetalle
use [Bundle]\[Bundle]Bundle\Form\EventListener\AddProveedoresFieldSubscriber; class OrdenesDetalleType extends AbstractType { /** * @param FormBuilderInterface $builder * @param array $options */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder //->add("productos") ->add('cantidad') ->add('precio') ->add('ivaAduanal') ->add('igi'); $builder->addEventSubscriber(new AddProveedoresFieldSubscriber()); } /** * @param OptionsResolverInterface $resolver */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'data_class' => '[Bundle]\[Bundle]Bundle\Entity\OrdenesDetalle' )); } /** * @return string */ public function getName() { return 'sisfc_operacionesbundle_ordenesproductos'; } }
Evento de formulario para agregar el campo Productos a partir del proveedor de la orden de compra:
class AddProveedoresFieldSubscriber implements EventSubscriberInterface { public static function getSubscribedEvents() { return array( FormEvents::PRE_SET_DATA => 'preSetData', // FormEvents::PRE_BIND => 'preBind' ); } public function preSetData(FormEvent $event) { $data = $event->getData(); $form = $event->getForm(); if (null === $data) { return; } $proveedorId = $data->getOrdenes()->getProveedores()->getId(); if ($data->getOrdenes()) { $form->add("productos",'entity', array( 'class' => 'ProveedoresBundle:Productos', 'property' => "producto", 'query_builder' => function(EntityRepository $er) use($proveedorId){ return $er->createQueryBuilder('p')->where('p.proveedores = :proveedor') ->setParameter('proveedor', $proveedorId); }, 'empty_value' => "Seleccione" ) ); } }
Hasta aquí todo funciona. El gran problema es en el momento de renderizar el formulario y usar el data-prototype
. En la clase OrdenesProductosType
quito el campo de Productos
y creo un evento de formulario para saber el id
del Proveedor
relacionado con la OrdenCompra
y así poder generar la consulta que necesito para llamar los productos de este proveedor.
<form > {% macro prototype(orden) %} <tr> <td>{{ form_widget(orden.productos) }}</td> <td>{{ form_widget(orden.cantidad) }} </td> <td>{{ form_widget(orden.precio) }} </td> <td>{{ form_widget(orden.ivaAduanal) }} </td> <td>{{ form_widget(orden.igi) }}</td> <td> <button class="btn btn-default btn-delete"><span class="glyphicon glyphicon-remove"></span></button> </td> </tr> {% endmacro %} <table class="table table-condensed"> <caption></caption> <thead> ... </thead> <tbody class="collections" data-prototype="{{ _self.prototype(edit_form.ordenes.vars.prototype)|e }}"> {% for orden in edit_form.ordenes %} {{_self.prototype(orden)}} {% endfor %} </tbody> </table> <div class="btn-group" role="group" aria-label="..."> <button type="submit" class="btn btn-primary"> <span class="glyphicon glyphicon-refresh" aria-hidden="true"></span> Actualizar </button> <button type="button" class="btn btn-default add"> <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Agregar </button> </div> </form>
Al parecer, prototype no es capaz de entender la información que se crea a partir del evento del formulario. Si lo dejo como está la aplicación me arroja el error:
Method "productos" for object "Symfony\Component\Form\FormView" does not exist in OperacionesBundle:OrdenesCompra:edit.html.twig
Es obvio, porque la línea productos, en la clase OrdenesDetalle
está comentada.
Si descomento la línea //->add("productos”) -> ->add("productos”)
la aplicación funciona normalmente, sólo que me arroja en la vista todos los productos de la aplicación.
Quedo atento a cualquier solución. De antemano, quedo agradecido. Saludos