From 8c6342bffe47853eea65a55aabd96ea15a353e03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Fri, 30 Aug 2019 14:25:05 +0200 Subject: [PATCH] Added an page for editing order informations --- config/services.yaml | 4 + src/Entity/Parts/Part.php | 18 ++- src/Entity/PriceInformations/Currency.php | 2 +- src/Entity/PriceInformations/Orderdetail.php | 51 +++++++- src/Entity/PriceInformations/Pricedetail.php | 20 ++- src/Form/AdminPages/CurrencyAdminForm.php | 1 + src/Form/Part/OrderdetailType.php | 82 ++++++++++++ src/Form/Part/PartBaseType.php | 8 ++ src/Form/Part/PricedetailType.php | 62 +++++++++ src/Form/Type/CurrencyEntityType.php | 118 ++++++++++++++++++ src/Form/Type/StructuralEntityType.php | 16 ++- templates/Parts/edit/_orderdetails.html.twig | 66 ++++++++++ .../Parts/edit/edit_form_styles.html.twig | 59 +++++++++ templates/Parts/edit/edit_part_info.html.twig | 9 ++ 14 files changed, 504 insertions(+), 12 deletions(-) create mode 100644 src/Form/Part/OrderdetailType.php create mode 100644 src/Form/Part/PricedetailType.php create mode 100644 src/Form/Type/CurrencyEntityType.php create mode 100644 templates/Parts/edit/_orderdetails.html.twig create mode 100644 templates/Parts/edit/edit_form_styles.html.twig diff --git a/config/services.yaml b/config/services.yaml index 2ce270f7..5374009d 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -52,6 +52,10 @@ services: arguments: $base_current: '%default_currency%' + App\Form\Type\CurrencyEntityType: + arguments: + $base_currency: '%default_currency%' + # add more service definitions when explicit configuration is needed # please note that last definitions always *replace* previous ones diff --git a/src/Entity/Parts/Part.php b/src/Entity/Parts/Part.php index eaeae5b2..5087ddf4 100644 --- a/src/Entity/Parts/Part.php +++ b/src/Entity/Parts/Part.php @@ -128,8 +128,8 @@ class Part extends AttachmentContainingDBElement /** * @var Orderdetail[] - * @ORM\OneToMany(targetEntity="App\Entity\PriceInformations\Orderdetail", mappedBy="part") - * + * @ORM\OneToMany(targetEntity="App\Entity\PriceInformations\Orderdetail", mappedBy="part", cascade={"persist", "remove"}, orphanRemoval=true) + * @Assert\Valid() * @ColumnSecurity(prefix="orderdetails", type="object") */ protected $orderdetails; @@ -279,6 +279,7 @@ class Part extends AttachmentContainingDBElement { parent::__construct(); $this->partLots = new ArrayCollection(); + $this->orderdetails = new ArrayCollection(); } /** @@ -510,6 +511,19 @@ class Part extends AttachmentContainingDBElement return $this->orderdetails; } + public function addOrderdetail(Orderdetail $orderdetail) : Part + { + $orderdetail->setPart($this); + $this->orderdetails->add($orderdetail); + return $this; + } + + public function removeOrderdetail(Orderdetail $orderdetail) : Part + { + $this->orderdetails->removeElement($orderdetail); + return $this; + } + /** * Get all devices which uses this part. * diff --git a/src/Entity/PriceInformations/Currency.php b/src/Entity/PriceInformations/Currency.php index b678beeb..5b848410 100644 --- a/src/Entity/PriceInformations/Currency.php +++ b/src/Entity/PriceInformations/Currency.php @@ -86,7 +86,7 @@ class Currency extends StructuralDBElement * @param string $iso_code * @return Currency */ - public function setIsoCode(string $iso_code): Currency + public function setIsoCode(?string $iso_code): Currency { $this->iso_code = $iso_code; return $this; diff --git a/src/Entity/PriceInformations/Orderdetail.php b/src/Entity/PriceInformations/Orderdetail.php index 11e8ee2c..733105c2 100644 --- a/src/Entity/PriceInformations/Orderdetail.php +++ b/src/Entity/PriceInformations/Orderdetail.php @@ -65,9 +65,12 @@ use App\Entity\Base\DBElement; use App\Entity\Base\TimestampTrait; use App\Entity\Parts\Part; use App\Entity\Parts\Supplier; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\PersistentCollection; use Exception; +use Symfony\Component\Form\Extension\Core\Type\CollectionType; use Symfony\Component\Validator\Constraints as Assert; /** @@ -85,6 +88,7 @@ class Orderdetail extends DBElement * @var Part * @ORM\ManyToOne(targetEntity="App\Entity\Parts\Part", inversedBy="orderdetails") * @ORM\JoinColumn(name="part_id", referencedColumnName="id") + * @Assert\NotNull() */ protected $part; @@ -96,7 +100,8 @@ class Orderdetail extends DBElement protected $supplier; /** - * @ORM\OneToMany(targetEntity="Pricedetail", mappedBy="orderdetail") + * @ORM\OneToMany(targetEntity="Pricedetail", mappedBy="orderdetail", cascade={"persist", "remove"}, orphanRemoval=true) + * @Assert\Valid() */ protected $pricedetails; @@ -119,6 +124,11 @@ class Orderdetail extends DBElement */ protected $supplier_product_url = ""; + public function __construct() + { + $this->pricedetails = new ArrayCollection(); + } + /** * Returns the ID as an string, defined by the element class. * This should have a form like P000014, for a part with ID 14. @@ -205,11 +215,34 @@ class Orderdetail extends DBElement * * @throws Exception if there was an error */ - public function getPricedetails(): PersistentCollection + public function getPricedetails(): Collection { return $this->pricedetails; } + /** + * Adds an pricedetail to this orderdetail + * @param Pricedetail $pricedetail The pricedetail to add + * @return Orderdetail + */ + public function addPricedetail(Pricedetail $pricedetail) : Orderdetail + { + $pricedetail->setOrderdetail($this); + $this->pricedetails->add($pricedetail); + return $this; + } + + /** + * Removes an pricedetail from this orderdetail + * @param Pricedetail $pricedetail + * @return Orderdetail + */ + public function removePricedetail(Pricedetail $pricedetail) : Orderdetail + { + $this->pricedetails->removeElement($pricedetail); + return $this; + } + /** * Get the price for a specific quantity. * @param int $quantity this is the quantity to choose the correct pricedetails @@ -264,6 +297,15 @@ class Orderdetail extends DBElement * *********************************************************************************/ + /** + * Sets a new part with which this orderdetail is associated + * @param Part $part + */ + public function setPart(Part $part) + { + $this->part = $part; + } + /** * Sets the new supplier associated with this orderdetail. * @param Supplier $new_supplier @@ -310,6 +352,11 @@ class Orderdetail extends DBElement */ public function setSupplierProductUrl(string $new_url) { + //Only change the internal URL if it is not the auto generated one + if ($new_url == $this->supplier->getAutoProductUrl($this->getSupplierPartNr())) { + return $this; + } + $this->supplier_product_url = $new_url; return $this; diff --git a/src/Entity/PriceInformations/Pricedetail.php b/src/Entity/PriceInformations/Pricedetail.php index c330003f..4b674e79 100644 --- a/src/Entity/PriceInformations/Pricedetail.php +++ b/src/Entity/PriceInformations/Pricedetail.php @@ -85,6 +85,7 @@ class Pricedetail extends DBElement * @var Orderdetail * @ORM\ManyToOne(targetEntity="Orderdetail", inversedBy="pricedetails") * @ORM\JoinColumn(name="orderdetails_id", referencedColumnName="id") + * @Assert\NotNull() */ protected $orderdetail; @@ -93,7 +94,7 @@ class Pricedetail extends DBElement * @ORM\Column(type="decimal", precision=11, scale=5) * @Assert\Positive() */ - protected $price; + protected $price = 0.0; /** * @var ?Currency The currency used for the current price information. @@ -109,19 +110,19 @@ class Pricedetail extends DBElement * @ORM\Column(type="integer") * @Assert\Positive() */ - protected $price_related_quantity; + protected $price_related_quantity = 1; /** * @var int * @ORM\Column(type="integer") */ - protected $min_discount_quantity; + protected $min_discount_quantity = 1; /** * @var bool * @ORM\Column(type="boolean") */ - protected $manual_input; + protected $manual_input = true; /******************************************************************************** @@ -212,6 +213,17 @@ class Pricedetail extends DBElement * *********************************************************************************/ + /** + * Sets the orderdetail to which this pricedetail belongs to. + * @param Orderdetail $orderdetail + * @return $this + */ + public function setOrderdetail(Orderdetail $orderdetail) + { + $this->orderdetail = $orderdetail; + return $this; + } + /** * Sets the currency associated with the price informations. * Set to null, to use the global base currency. diff --git a/src/Form/AdminPages/CurrencyAdminForm.php b/src/Form/AdminPages/CurrencyAdminForm.php index 56478c11..fcfc148a 100644 --- a/src/Form/AdminPages/CurrencyAdminForm.php +++ b/src/Form/AdminPages/CurrencyAdminForm.php @@ -45,6 +45,7 @@ class CurrencyAdminForm extends BaseEntityAdminForm $is_new = $entity->getID() === null; $builder->add('iso_code', CurrencyType::class , ['required' => true, + 'required' => false, 'label' => 'currency.iso_code.label', 'preferred_choices' => ['EUR', 'USD', 'GBP', 'JPY', 'CNY'], 'attr' => ['class' => 'selectpicker', 'data-live-search' => true], diff --git a/src/Form/Part/OrderdetailType.php b/src/Form/Part/OrderdetailType.php new file mode 100644 index 00000000..30aad202 --- /dev/null +++ b/src/Form/Part/OrderdetailType.php @@ -0,0 +1,82 @@ +add('supplierpartnr', TextType::class, [ + 'required' => false, + 'empty_data' => "" + ]); + + $builder->add('supplier', StructuralEntityType::class, ['class' => Supplier::class, 'disable_not_selectable' => true]); + + $builder->add('supplierProductUrl', UrlType::class, [ + 'required' => false, + 'empty_data' => "" + ]); + + $builder->add('obsolete', CheckboxType::class, [ + 'required' => false, + 'label_attr' => ['class' => 'checkbox-custom'] + ]); + + //Attachment section + $builder->add('priceDetails', CollectionType::class, [ + 'entry_type' => PricedetailType::class, + 'allow_add' => true, 'allow_delete' => true, + 'label' => false, + 'by_reference' => false + ]); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'data_class' => Orderdetail::class, + 'error_bubbling' => false, + ]); + } +} \ No newline at end of file diff --git a/src/Form/Part/PartBaseType.php b/src/Form/Part/PartBaseType.php index a708587d..f9315edd 100644 --- a/src/Form/Part/PartBaseType.php +++ b/src/Form/Part/PartBaseType.php @@ -144,6 +144,14 @@ class PartBaseType extends AbstractType 'by_reference' => false ]); + //Attachment section + $builder->add('orderDetails', CollectionType::class, [ + 'entry_type' => OrderdetailType::class, + 'allow_add' => true, 'allow_delete' => true, + 'label' => false, + 'by_reference' => false, + ]); + $builder //Buttons ->add('save', SubmitType::class, ['label' => 'part.edit.save']) diff --git a/src/Form/Part/PricedetailType.php b/src/Form/Part/PricedetailType.php new file mode 100644 index 00000000..ff980270 --- /dev/null +++ b/src/Form/Part/PricedetailType.php @@ -0,0 +1,62 @@ +add("minDiscountQuantity", IntegerType::class); + $builder->add("priceRelatedQuantity", IntegerType::class); + $builder->add("price", NumberType::class); + $builder->add("currency", CurrencyEntityType::class, ['required' => false]); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'data_class' => Pricedetail::class, + ]); + } +} \ No newline at end of file diff --git a/src/Form/Type/CurrencyEntityType.php b/src/Form/Type/CurrencyEntityType.php new file mode 100644 index 00000000..a93292a6 --- /dev/null +++ b/src/Form/Type/CurrencyEntityType.php @@ -0,0 +1,118 @@ +base_currency = $base_currency; + } + + public function configureOptions(OptionsResolver $resolver) + { + //Important to call the parent resolver! + parent::configureOptions($resolver); + + $resolver->setDefault('class', Currency::class); + $resolver->setDefault('disable_not_selectable', true); + + // This options allows you to override the currency shown for the null value + $resolver->setDefault('base_currency', null); + + $resolver->setDefault('empty_message', function (Options $options) { + //By default we use the global base currency: + $iso_code = $this->base_currency; + + if ($options['base_currency']) { //Allow to override it + $iso_code = $options['base_currency']; + } + + return Currencies::getSymbol($iso_code); + }); + } + + public function generateChoiceLabels(StructuralDBElement $choice, $key, $value): string + { + //Similar to StructuralEntityType, but we use the currency symbol instead if available + + /** @var StructuralDBElement|null $parent */ + $parent = $this->options['subentities_of']; + + /*** @var Currency $choice */ + $level = $choice->getLevel(); + //If our base entity is not the root level, we need to change the level, to get zero position + if ($this->options['subentities_of'] !== null) { + $level -= $parent->getLevel() - 1; + } + + + $tmp = str_repeat('   ', $choice->getLevel()); //Use 3 spaces for intendation + if (empty($choice->getIsoCode())) { + $tmp .= htmlspecialchars($choice->getName()); + } else { + $tmp .= Currencies::getSymbol($choice->getIsoCode()); + } + return $tmp; + } + + protected function generateChoiceAttr(StructuralDBElement $choice, $key, $value) : array + { + /** @var Currency $choice */ + + $tmp = array(); + + if (!empty($choice->getIsoCode())) { + //Show the name of the currency + $tmp += ['data-subtext' => $choice->getName()]; + } + + //Disable attribute if the choice is marked as not selectable + if ($this->options['disable_not_selectable'] && $choice->isNotSelectable()) { + $tmp += ['disabled' => 'disabled']; + } + return $tmp; + } +} \ No newline at end of file diff --git a/src/Form/Type/StructuralEntityType.php b/src/Form/Type/StructuralEntityType.php index 44c7b5b5..a8939740 100644 --- a/src/Form/Type/StructuralEntityType.php +++ b/src/Form/Type/StructuralEntityType.php @@ -77,14 +77,14 @@ class StructuralEntityType extends AbstractType function ($value) use ($options){ return $this->transform($value, $options); }, function ($value) use ($options) { - return $this->reverseTransform($value, $options); + return $this->reverseTransform($value, $options); })); } public function configureOptions(OptionsResolver $resolver) { $resolver->setRequired(['class']); - $resolver->setDefaults(['attr' => ['class' => 'selectpicker', 'data-live-search' => true], + $resolver->setDefaults([ 'show_fullpath_in_subtext' => true, //When this is enabled, the full path will be shown in subtext 'subentities_of' => null, //Only show entities with the given parent class 'disable_not_selectable' => false, //Disable entries with not selectable property @@ -99,6 +99,16 @@ class StructuralEntityType extends AbstractType return $this->generateChoiceAttr($choice, $key, $value); } ]); + + $resolver->setDefault('empty_message', null); + + $resolver->setDefault('attr', function (Options $options) { + $tmp = ['class' => 'selectpicker', 'data-live-search' => true]; + if ($options['empty_message']) { + $tmp['data-none-Selected-Text'] = $options['empty_message']; + } + return $tmp; + }); } protected function generateChoiceAttr(StructuralDBElement $choice, $key, $value) : array @@ -130,7 +140,7 @@ class StructuralEntityType extends AbstractType $tmp = str_repeat('   ', $choice->getLevel()); //Use 3 spaces for intendation - $tmp .= htmlspecialchars($choice->getName($parent)); + $tmp .= htmlspecialchars($choice->getName()); return $tmp; } diff --git a/templates/Parts/edit/_orderdetails.html.twig b/templates/Parts/edit/_orderdetails.html.twig new file mode 100644 index 00000000..23bb5240 --- /dev/null +++ b/templates/Parts/edit/_orderdetails.html.twig @@ -0,0 +1,66 @@ +{% form_theme form 'Parts/edit/edit_form_styles.html.twig' %} + + + + {% for detail in form.orderDetails %} + {{ form_widget(detail) }} + {% endfor %} + +
+ + + + \ No newline at end of file diff --git a/templates/Parts/edit/edit_form_styles.html.twig b/templates/Parts/edit/edit_form_styles.html.twig new file mode 100644 index 00000000..a42798d6 --- /dev/null +++ b/templates/Parts/edit/edit_form_styles.html.twig @@ -0,0 +1,59 @@ +{% block pricedetail_widget %} + + {{ form_widget(form.minDiscountQuantity) }} {{ form_errors(form.minDiscountQuantity) }} + +
+ {{ form_widget(form.price) }} +
+ {{ form_widget(form.currency) }} +
+
+ {{ form_errors(form.price) }} + {{ form_errors(form.currency) }} + + {{ form_widget(form.priceRelatedQuantity) }} {{ form_errors(form.price) }} + + + +{% endblock %} + +{% block orderdetail_widget %} + + + {{ form_row(form.supplierpartnr) }} + {{ form_row(form.supplier) }} + {{ form_row(form.supplierProductUrl) }} + {{ form_row(form.obsolete) }} + + + + + + + + + + + + + {% for price in form.priceDetails %} + {{ form_widget(price) }} + {% endfor %} + +
Min Qty.PricePrice Qty
+ + + + + + + +{% endblock %} \ No newline at end of file diff --git a/templates/Parts/edit/edit_part_info.html.twig b/templates/Parts/edit/edit_part_info.html.twig index d498a6c8..14900a19 100644 --- a/templates/Parts/edit/edit_part_info.html.twig +++ b/templates/Parts/edit/edit_part_info.html.twig @@ -46,6 +46,12 @@ {% trans %}part.edit.tab.attachments{% endtrans %} +