Added an page for editing order informations

This commit is contained in:
Jan Böhmer 2019-08-30 14:25:05 +02:00
parent 1776cd9a77
commit 8c6342bffe
14 changed files with 504 additions and 12 deletions

View file

@ -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

View file

@ -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.
*

View file

@ -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;

View file

@ -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;

View file

@ -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.

View file

@ -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],

View file

@ -0,0 +1,82 @@
<?php
/**
*
* part-db version 0.1
* Copyright (C) 2005 Christoph Lechner
* http://www.cl-projects.de/
*
* part-db version 0.2+
* Copyright (C) 2009 K. Jacobs and others (see authors.php)
* http://code.google.com/p/part-db/
*
* Part-DB Version 0.4+
* Copyright (C) 2016 - 2019 Jan Böhmer
* https://github.com/jbtronics
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
*/
namespace App\Form\Part;
use App\Entity\Parts\Supplier;
use App\Entity\PriceInformations\Orderdetail;
use App\Form\Type\StructuralEntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\UrlType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class OrderdetailType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->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,
]);
}
}

View file

@ -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'])

View file

@ -0,0 +1,62 @@
<?php
/**
*
* part-db version 0.1
* Copyright (C) 2005 Christoph Lechner
* http://www.cl-projects.de/
*
* part-db version 0.2+
* Copyright (C) 2009 K. Jacobs and others (see authors.php)
* http://code.google.com/p/part-db/
*
* Part-DB Version 0.4+
* Copyright (C) 2016 - 2019 Jan Böhmer
* https://github.com/jbtronics
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
*/
namespace App\Form\Part;
use App\Entity\PriceInformations\Currency;
use App\Entity\PriceInformations\Orderdetail;
use App\Entity\PriceInformations\Pricedetail;
use App\Form\Type\CurrencyEntityType;
use App\Form\Type\StructuralEntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class PricedetailType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->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,
]);
}
}

View file

@ -0,0 +1,118 @@
<?php
/**
*
* part-db version 0.1
* Copyright (C) 2005 Christoph Lechner
* http://www.cl-projects.de/
*
* part-db version 0.2+
* Copyright (C) 2009 K. Jacobs and others (see authors.php)
* http://code.google.com/p/part-db/
*
* Part-DB Version 0.4+
* Copyright (C) 2016 - 2019 Jan Böhmer
* https://github.com/jbtronics
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
*/
namespace App\Form\Type;
use App\Entity\Base\StructuralDBElement;
use App\Entity\PriceInformations\Currency;
use App\Services\TreeBuilder;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Intl\Currencies;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
class CurrencyEntityType extends StructuralEntityType
{
protected $base_currency;
public function __construct(EntityManagerInterface $em, TreeBuilder $builder, $base_currency)
{
parent::__construct($em, $builder);
$this->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('&nbsp;&nbsp;&nbsp;', $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;
}
}

View file

@ -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('&nbsp;&nbsp;&nbsp;', $choice->getLevel()); //Use 3 spaces for intendation
$tmp .= htmlspecialchars($choice->getName($parent));
$tmp .= htmlspecialchars($choice->getName());
return $tmp;
}

View file

@ -0,0 +1,66 @@
{% form_theme form 'Parts/edit/edit_form_styles.html.twig' %}
<table class="table table-striped" id="orderdetails_table" data-prototype="{{ form_widget(form.orderDetails.vars.prototype)|e('html_attr') }}">
<tbody>
{% for detail in form.orderDetails %}
{{ form_widget(detail) }}
{% endfor %}
</tbody>
</table>
<button type="button" class="btn btn-success" onclick="create_orderdetail_entry(this)">
<i class="fas fa-plus-square fa-fw"></i>
{% trans %}orderdetail.create{% endtrans %}
</button>
<script>
function delete_pricedetail_entry(btn) {
window.bootbox.confirm('{% trans %}pricedetails.edit.delete.confirm{% endtrans %}', function (result) {
if(result) {
$(btn).closest("tr").remove();
}
});
}
function create_pricedetail_entry(btn) {
//Determine the table, so we can determine, how many entries there are already.
$holder = $(btn).siblings("table");
var index = $holder.find(":input").length;
var newForm = $holder.data("prototype");
//Increase the index
newForm = newForm.replace(/__name__/g, index);
$holder.children("tbody").append(newForm);
//Reinit the selectpickers
$(".selectpicker").selectpicker();
}
function delete_orderdetail_entry(btn) {
window.bootbox.confirm('{% trans %}orderdetails.edit.delete.confirm{% endtrans %}', function (result) {
if(result) {
$(btn).parents("tr").remove();
}
});
}
function create_orderdetail_entry(btn) {
//Determine the table, so we can determine, how many entries there are already.
$holder = $("#orderdetails_table");
var index = $holder.find(":input").length;
var newForm = $holder.data("prototype");
//Increase the index
newForm = newForm.replace(/__name__/g, index);
$holder.children("tbody").append(newForm);
//Reinit the selectpickers
$(".selectpicker").selectpicker();
}
</script>

View file

@ -0,0 +1,59 @@
{% block pricedetail_widget %}
<tr>
<td>{{ form_widget(form.minDiscountQuantity) }} {{ form_errors(form.minDiscountQuantity) }}</td>
<td>
<div class="input-group">
{{ form_widget(form.price) }}
<div class="input-group-append">
{{ form_widget(form.currency) }}
</div>
</div>
{{ form_errors(form.price) }}
{{ form_errors(form.currency) }}
</td>
<td>{{ form_widget(form.priceRelatedQuantity) }} {{ form_errors(form.price) }}</td>
<td><button type="button" class="btn btn-danger order_btn_delete" title="{% trans %}orderdetail.delete{% endtrans %}" onclick="delete_pricedetail_entry(this);">
<i class="fas fa-trash-alt fa-fw"></i>
</button>
</td>
</tr>
{% endblock %}
{% block orderdetail_widget %}
<tr>
<td>
{{ form_row(form.supplierpartnr) }}
{{ form_row(form.supplier) }}
{{ form_row(form.supplierProductUrl) }}
{{ form_row(form.obsolete) }}
</td>
<td>
<table class="table table-sm table-bordered" data-prototype="{{ form_widget(form.priceDetails.vars.prototype)|e('html_attr') }}">
<thead>
<tr>
<th>Min Qty.</th>
<th>Price</th>
<th>Price Qty</th>
<th></th>
</tr>
</thead>
<tbody>
{% for price in form.priceDetails %}
{{ form_widget(price) }}
{% endfor %}
</tbody>
</table>
<button type="button" class="btn btn-success" onclick="create_pricedetail_entry(this)">
<i class="fas fa-plus-square fa-fw"></i>
{% trans %}pricedetail.create{% endtrans %}
</button>
</td>
<td>
<button type="button" class="btn btn-danger order_btn_delete" onclick="delete_orderdetail_entry(this);">
<i class="fas fa-trash-alt fa-fw"></i>
{% trans %}orderdetail.delete{% endtrans %}
</button>
</td>
</tr>
{% endblock %}

View file

@ -46,6 +46,12 @@
{% trans %}part.edit.tab.attachments{% endtrans %}
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="tab" role="tab" href="#orderdetails">
<i class="fas fa-shopping-cart fa-fw"></i>
{% trans %}part.edit.tab.orderdetails{% endtrans %}
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="tab" role="tab" href="#comment">
<i class="fas fa-sticky-note fa-fw"></i>
@ -72,6 +78,9 @@
<div class="tab-pane fade p-2" id="attachments" role="tabpanel">
{% include "Parts/edit/_attachments.html.twig" %}
</div>
<div class="tab-pane fade p-2" id="orderdetails" role="tabpanel">
{% include "Parts/edit/_orderdetails.html.twig" %}
</div>
<div class="tab-pane fade p-2" id="comment" role="tabpanel">
{{ form_widget(form.comment)}}
</div>