mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-24 18:58:46 +02:00
Merge branch 'orderdetails_edit'
This commit is contained in:
commit
e0b171d240
32 changed files with 1142 additions and 268 deletions
|
@ -620,6 +620,10 @@ BS 4 overrides
|
|||
}
|
||||
}
|
||||
|
||||
.form-group > label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
label:not(.form-check-label, custom-control-label) {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
"doctrine/annotations": "^1.6",
|
||||
"florianv/swap": "^4.0",
|
||||
"friendsofsymfony/ckeditor-bundle": "^2.0",
|
||||
"gerardojbaez/money": "^0.3.1",
|
||||
"nyholm/psr7": "^1.1",
|
||||
"ocramius/proxy-manager": "2.1.*",
|
||||
"omines/datatables-bundle": "^0.2.2",
|
||||
|
@ -43,7 +42,8 @@
|
|||
"twig/extensions": "^1.5",
|
||||
"twig/extra-bundle": "3.x-dev",
|
||||
"twig/intl-extra": "3.x-dev",
|
||||
"webmozart/assert": "^1.4"
|
||||
"webmozart/assert": "^1.4",
|
||||
"ext-bcmath": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"roave/security-advisories": "dev-master",
|
||||
|
|
85
composer.lock
generated
85
composer.lock
generated
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "f66e6f1196a0dbebea3453e306b7e58f",
|
||||
"content-hash": "ce7f2c4f8eb45f90abbc99599f3fbe83",
|
||||
"packages": [
|
||||
{
|
||||
"name": "clue/stream-filter",
|
||||
|
@ -1570,54 +1570,6 @@
|
|||
],
|
||||
"time": "2019-04-15T16:29:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "gerardojbaez/money",
|
||||
"version": "v0.3.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/gerardojbaez/money.git",
|
||||
"reference": "1a29ca19899fad8ae559e9f2c982815ea21f8a6c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/gerardojbaez/money/zipball/1a29ca19899fad8ae559e9f2c982815ea21f8a6c",
|
||||
"reference": "1a29ca19899fad8ae559e9f2c982815ea21f8a6c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.5.9"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "5.4.*"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Gerardojbaez\\Money\\": "src/"
|
||||
},
|
||||
"files": [
|
||||
"src/helpers.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Gerardo Báez",
|
||||
"email": "gerardojbaez@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "A simple and cross-platform alternative to PHP money_format(). 91 currencies supported, including INR.",
|
||||
"keywords": [
|
||||
"currency",
|
||||
"formatter",
|
||||
"money",
|
||||
"money_format"
|
||||
],
|
||||
"time": "2018-02-24T18:59:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/guzzle",
|
||||
"version": "6.3.3",
|
||||
|
@ -7379,16 +7331,16 @@
|
|||
},
|
||||
{
|
||||
"name": "zendframework/zend-code",
|
||||
"version": "3.3.1",
|
||||
"version": "3.3.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/zendframework/zend-code.git",
|
||||
"reference": "c21db169075c6ec4b342149f446e7b7b724f95eb"
|
||||
"reference": "936fa7ad4d53897ea3e3eb41b5b760828246a20b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/zendframework/zend-code/zipball/c21db169075c6ec4b342149f446e7b7b724f95eb",
|
||||
"reference": "c21db169075c6ec4b342149f446e7b7b724f95eb",
|
||||
"url": "https://api.github.com/repos/zendframework/zend-code/zipball/936fa7ad4d53897ea3e3eb41b5b760828246a20b",
|
||||
"reference": "936fa7ad4d53897ea3e3eb41b5b760828246a20b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -7396,10 +7348,10 @@
|
|||
"zendframework/zend-eventmanager": "^2.6 || ^3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/annotations": "~1.0",
|
||||
"doctrine/annotations": "^1.0",
|
||||
"ext-phar": "*",
|
||||
"phpunit/phpunit": "^6.2.3",
|
||||
"zendframework/zend-coding-standard": "^1.0.0",
|
||||
"phpunit/phpunit": "^7.5.15",
|
||||
"zendframework/zend-coding-standard": "^1.0",
|
||||
"zendframework/zend-stdlib": "^2.7 || ^3.0"
|
||||
},
|
||||
"suggest": {
|
||||
|
@ -7422,13 +7374,13 @@
|
|||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"description": "provides facilities to generate arbitrary code using an object oriented interface",
|
||||
"homepage": "https://github.com/zendframework/zend-code",
|
||||
"description": "Extensions to the PHP Reflection API, static code scanning, and code generation",
|
||||
"keywords": [
|
||||
"ZendFramework",
|
||||
"code",
|
||||
"zf2"
|
||||
"zf"
|
||||
],
|
||||
"time": "2018-08-13T20:36:59+00:00"
|
||||
"time": "2019-08-31T14:14:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "zendframework/zend-eventmanager",
|
||||
|
@ -7538,16 +7490,16 @@
|
|||
},
|
||||
{
|
||||
"name": "nikic/php-parser",
|
||||
"version": "v4.2.3",
|
||||
"version": "v4.2.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nikic/PHP-Parser.git",
|
||||
"reference": "e612609022e935f3d0337c1295176505b41188c8"
|
||||
"reference": "97e59c7a16464196a8b9c77c47df68e4a39a45c4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/e612609022e935f3d0337c1295176505b41188c8",
|
||||
"reference": "e612609022e935f3d0337c1295176505b41188c8",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/97e59c7a16464196a8b9c77c47df68e4a39a45c4",
|
||||
"reference": "97e59c7a16464196a8b9c77c47df68e4a39a45c4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -7585,7 +7537,7 @@
|
|||
"parser",
|
||||
"php"
|
||||
],
|
||||
"time": "2019-08-12T20:17:41+00:00"
|
||||
"time": "2019-09-01T07:51:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "roave/security-advisories",
|
||||
|
@ -8476,7 +8428,8 @@
|
|||
"ext-ctype": "*",
|
||||
"ext-iconv": "*",
|
||||
"ext-intl": "*",
|
||||
"ext-mbstring": "*"
|
||||
"ext-mbstring": "*",
|
||||
"ext-bcmath": "*"
|
||||
},
|
||||
"platform-dev": []
|
||||
}
|
||||
|
|
|
@ -52,6 +52,17 @@ services:
|
|||
arguments:
|
||||
$base_current: '%default_currency%'
|
||||
|
||||
App\Form\Type\CurrencyEntityType:
|
||||
arguments:
|
||||
$base_currency: '%default_currency%'
|
||||
|
||||
App\Services\PricedetailHelper:
|
||||
arguments:
|
||||
$base_currency: '%default_currency%'
|
||||
|
||||
App\Services\MoneyFormatter:
|
||||
arguments:
|
||||
$base_currency: '%default_currency%'
|
||||
|
||||
# add more service definitions when explicit configuration is needed
|
||||
# please note that last definitions always *replace* previous ones
|
||||
|
|
|
@ -35,6 +35,7 @@ use App\Entity\Parts\Part;
|
|||
use App\Form\AttachmentFormType;
|
||||
use App\Form\Part\PartBaseType;
|
||||
use App\Services\AttachmentHelper;
|
||||
use App\Services\PricedetailHelper;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
|
@ -51,14 +52,15 @@ class PartController extends AbstractController
|
|||
* @param AttachmentHelper $attachmentHelper
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function show(Part $part, AttachmentHelper $attachmentHelper)
|
||||
public function show(Part $part, AttachmentHelper $attachmentHelper, PricedetailHelper $pricedetailHelper)
|
||||
{
|
||||
$this->denyAccessUnlessGranted('read', $part);
|
||||
|
||||
return $this->render('Parts/info/show_part_info.html.twig',
|
||||
[
|
||||
'part' => $part,
|
||||
'attachment_helper' => $attachmentHelper
|
||||
'attachment_helper' => $attachmentHelper,
|
||||
'pricedetail_helper' => $pricedetailHelper
|
||||
]
|
||||
);
|
||||
}
|
||||
|
@ -102,7 +104,7 @@ class PartController extends AbstractController
|
|||
[
|
||||
'part' => $part,
|
||||
'form' => $form->createView(),
|
||||
'attachment_helper' => $attachmentHelper
|
||||
'attachment_helper' => $attachmentHelper,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||
* Class Attachment.
|
||||
*
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="`attachements`")
|
||||
* @ORM\Table(name="`attachments`")
|
||||
* @ORM\InheritanceType("SINGLE_TABLE")
|
||||
* @ORM\DiscriminatorColumn(name="class_name", type="string")
|
||||
* @ORM\DiscriminatorMap({"PartDB\Part" = "PartAttachment", "Part" = "PartAttachment"})
|
||||
|
|
|
@ -56,12 +56,13 @@ use App\Entity\Base\StructuralDBElement;
|
|||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use s9e\TextFormatter\Configurator\RendererGenerators\PHP\XPathConvertor\Convertors\SingleByteStringManipulation;
|
||||
|
||||
/**
|
||||
* Class AttachmentType.
|
||||
*
|
||||
* @ORM\Entity(repositoryClass="App\Repository\StructuralDBElementRepository")
|
||||
* @ORM\Table(name="`attachement_types`")
|
||||
* @ORM\Table(name="`attachment_types`")
|
||||
*/
|
||||
class AttachmentType extends StructuralDBElement
|
||||
{
|
||||
|
@ -82,6 +83,12 @@ class AttachmentType extends StructuralDBElement
|
|||
*/
|
||||
protected $parent;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @ORM\Column(type="string", length=65535)
|
||||
*/
|
||||
protected $filetype_filter;
|
||||
|
||||
/**
|
||||
* Get all attachements ("Attachement" objects) with this type.
|
||||
*
|
||||
|
@ -98,6 +105,27 @@ class AttachmentType extends StructuralDBElement
|
|||
return $this->attachments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an filter, which file types are allowed for attachment files.
|
||||
* @return string
|
||||
*/
|
||||
public function getFiletypeFilter(): string
|
||||
{
|
||||
return $this->filetype_filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filetype_filter
|
||||
* @return AttachmentType
|
||||
*/
|
||||
public function setFiletypeFilter(string $filetype_filter): AttachmentType
|
||||
{
|
||||
$this->filetype_filter = $filetype_filter;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the ID as an string, defined by the element class.
|
||||
* This should have a form like P000014, for a part with ID 14.
|
||||
|
|
|
@ -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;
|
||||
|
@ -243,6 +243,12 @@ class Part extends AttachmentContainingDBElement
|
|||
*/
|
||||
protected $manufacturer_product_number = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
*/
|
||||
protected $manufacturing_status;
|
||||
|
||||
/**
|
||||
* @var bool Determines if this part entry needs review (for example, because it is work in progress)
|
||||
* @ORM\Column(type="boolean")
|
||||
|
@ -273,6 +279,7 @@ class Part extends AttachmentContainingDBElement
|
|||
{
|
||||
parent::__construct();
|
||||
$this->partLots = new ArrayCollection();
|
||||
$this->orderdetails = new ArrayCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -504,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.
|
||||
*
|
||||
|
@ -517,68 +537,6 @@ class Part extends AttachmentContainingDBElement
|
|||
return $this->devices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all prices of this part.
|
||||
*
|
||||
* This method simply gets the prices of the orderdetails and prepare them.\n
|
||||
* In the returned array/string there is a price for every supplier.
|
||||
* @param int $quantity this is the quantity to choose the correct priceinformation
|
||||
* @param int|null $multiplier * This is the multiplier which will be applied to every single price
|
||||
* * If you pass NULL, the number from $quantity will be used
|
||||
* @param bool $hide_obsolete If true, prices from obsolete orderdetails will NOT be returned
|
||||
*
|
||||
* @return float[] all prices as an array of floats (if "$delimeter == NULL" & "$float_array == true")
|
||||
*
|
||||
* @throws \Exception if there was an error
|
||||
*/
|
||||
public function getPrices(int $quantity = 1, $multiplier = null, bool $hide_obsolete = false) : array
|
||||
{
|
||||
$prices = array();
|
||||
|
||||
foreach ($this->getOrderdetails($hide_obsolete) as $details) {
|
||||
$prices[] = $details->getPrice($quantity, $multiplier);
|
||||
}
|
||||
|
||||
return $prices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the average price of all orderdetails.
|
||||
*
|
||||
* With the $multiplier you're able to multiply the price before it will be returned.
|
||||
* This is useful if you want to have the price as a string with currency, but multiplied with a factor.
|
||||
*
|
||||
* @param int $quantity this is the quantity to choose the correct priceinformations
|
||||
* @param int|null $multiplier * This is the multiplier which will be applied to every single price
|
||||
* * If you pass NULL, the number from $quantity will be used
|
||||
*
|
||||
* @return float price (if "$as_money_string == false")
|
||||
*
|
||||
* @throws \Exception if there was an error
|
||||
*/
|
||||
public function getAveragePrice(int $quantity = 1, $multiplier = null) : ?float
|
||||
{
|
||||
$prices = $this->getPrices($quantity, $multiplier, true);
|
||||
//Findout out
|
||||
|
||||
$average_price = null;
|
||||
|
||||
$count = 0;
|
||||
foreach ($this->getOrderdetails() as $orderdetail) {
|
||||
$price = $orderdetail->getPrice(1, null);
|
||||
if (null !== $price) {
|
||||
$average_price += $price;
|
||||
++$count;
|
||||
}
|
||||
}
|
||||
|
||||
if ($count > 0) {
|
||||
$average_price /= $count;
|
||||
}
|
||||
|
||||
return $average_price;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this part is marked, for that it needs further review.
|
||||
* @return bool
|
||||
|
|
|
@ -34,6 +34,7 @@ namespace App\Entity\PriceInformations;
|
|||
|
||||
use App\Entity\Base\StructuralDBElement;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use s9e\TextFormatter\Configurator\TemplateNormalizations\AbstractChooseOptimization;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
|
@ -47,6 +48,8 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||
class Currency extends StructuralDBElement
|
||||
{
|
||||
|
||||
public const PRICE_SCALE = 5;
|
||||
|
||||
/**
|
||||
* @var string The 3 letter ISO code of the currency.
|
||||
* @ORM\Column(type="string")
|
||||
|
@ -55,7 +58,7 @@ class Currency extends StructuralDBElement
|
|||
protected $iso_code;
|
||||
|
||||
/**
|
||||
* @var float|null The exchange rate between this currency and the base currency
|
||||
* @var string|null The exchange rate between this currency and the base currency
|
||||
* (how many base units the current currency is worth)
|
||||
* @ORM\Column(type="decimal", precision=11, scale=5, nullable=true)
|
||||
* @Assert\Positive()
|
||||
|
@ -86,7 +89,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;
|
||||
|
@ -94,34 +97,34 @@ class Currency extends StructuralDBElement
|
|||
|
||||
/**
|
||||
* Returns the inverse exchange rate (how many of the current currency the base unit is worth)
|
||||
* @return float|null
|
||||
* @return string|null
|
||||
*/
|
||||
public function getInverseExchangeRate(): ?float
|
||||
public function getInverseExchangeRate(): ?string
|
||||
{
|
||||
$tmp = $this->getExchangeRate();
|
||||
|
||||
if ($tmp === null) {
|
||||
if ($tmp === null || (float) $tmp === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return 1 / $tmp;
|
||||
return bcdiv(1, $tmp, static::PRICE_SCALE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns The exchange rate between this currency and the base currency
|
||||
* (how many base units the current currency is worth)
|
||||
* @return float|null
|
||||
* @return string|null
|
||||
*/
|
||||
public function getExchangeRate(): ?float
|
||||
public function getExchangeRate(): ?string
|
||||
{
|
||||
return $this->exchange_rate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float|null $exchange_rate
|
||||
* @param string|null $exchange_rate
|
||||
* @return Currency
|
||||
*/
|
||||
public function setExchangeRate(?float $exchange_rate): Currency
|
||||
public function setExchangeRate(?string $exchange_rate): Currency
|
||||
{
|
||||
$this->exchange_rate = $exchange_rate;
|
||||
return $this;
|
||||
|
|
|
@ -62,24 +62,33 @@ declare(strict_types=1);
|
|||
namespace App\Entity\PriceInformations;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Class Orderdetail.
|
||||
*
|
||||
* @ORM\Table("`orderdetails`")
|
||||
* @ORM\Entity()
|
||||
* @ORM\HasLifecycleCallbacks()
|
||||
*/
|
||||
class Orderdetail extends DBElement
|
||||
{
|
||||
use TimestampTrait;
|
||||
|
||||
/**
|
||||
* @var Part
|
||||
* @ORM\ManyToOne(targetEntity="App\Entity\Parts\Part", inversedBy="orderdetails")
|
||||
* @ORM\JoinColumn(name="part_id", referencedColumnName="id")
|
||||
* @Assert\NotNull()
|
||||
*/
|
||||
protected $part;
|
||||
|
||||
|
@ -91,7 +100,9 @@ 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()
|
||||
* @ORM\OrderBy({"min_discount_quantity" = "ASC"})
|
||||
*/
|
||||
protected $pricedetails;
|
||||
|
||||
|
@ -99,25 +110,25 @@ class Orderdetail extends DBElement
|
|||
* @var string
|
||||
* @ORM\Column(type="string")
|
||||
*/
|
||||
protected $supplierpartnr;
|
||||
protected $supplierpartnr = "";
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* @ORM\Column(type="boolean")
|
||||
*/
|
||||
protected $obsolete;
|
||||
protected $obsolete = false;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @ORM\Column(type="string")
|
||||
* @Assert\Url()
|
||||
*/
|
||||
protected $supplier_product_url;
|
||||
protected $supplier_product_url = "";
|
||||
|
||||
/**
|
||||
* @var \DateTime The date when this element was created.
|
||||
* @ORM\Column(type="datetimetz", name="datetime_added")
|
||||
*/
|
||||
protected $addedDate;
|
||||
public function __construct()
|
||||
{
|
||||
$this->pricedetails = new ArrayCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID as an string, defined by the element class.
|
||||
|
@ -151,7 +162,7 @@ class Orderdetail extends DBElement
|
|||
*
|
||||
* @return Supplier the supplier of this orderdetails
|
||||
*/
|
||||
public function getSupplier(): Supplier
|
||||
public function getSupplier(): ?Supplier
|
||||
{
|
||||
return $this->supplier;
|
||||
}
|
||||
|
@ -180,17 +191,6 @@ class Orderdetail extends DBElement
|
|||
return (bool) $this->obsolete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the date/time when the element was created.
|
||||
* Returns null if the element was not yet saved to DB yet.
|
||||
*
|
||||
* @return \DateTime|null The creation time of the part.
|
||||
*/
|
||||
public function getAddedDate(): ?\DateTime
|
||||
{
|
||||
return $this->addedDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the link to the website of the article on the suppliers website.
|
||||
*
|
||||
|
@ -205,68 +205,72 @@ class Orderdetail extends DBElement
|
|||
return $this->supplier_product_url;
|
||||
}
|
||||
|
||||
if ($this->supplier === null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return $this->getSupplier()->getAutoProductUrl($this->supplierpartnr); // maybe an automatic url is available...
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all pricedetails.
|
||||
*
|
||||
* @return Pricedetail[] all pricedetails as a one-dimensional array of Pricedetails objects,
|
||||
* @return Pricedetail[]|Collection all pricedetails as a one-dimensional array of Pricedetails objects,
|
||||
* sorted by minimum discount quantity
|
||||
*
|
||||
* @throws Exception if there was an error
|
||||
*/
|
||||
public function getPricedetails(): PersistentCollection
|
||||
public function getPricedetails(): Collection
|
||||
{
|
||||
return $this->pricedetails;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the price for a specific quantity.
|
||||
* @param int $quantity this is the quantity to choose the correct pricedetails
|
||||
* @param int|null $multiplier * This is the multiplier which will be applied to every single price
|
||||
* * If you pass NULL, the number from $quantity will be used
|
||||
*
|
||||
* @return float float: the price as a float number (if "$as_money_string == false")
|
||||
*
|
||||
* @throws Exception if there are no pricedetails for the choosed quantity
|
||||
* (for example, there are only one pricedetails with the minimum discount quantity '10',
|
||||
* but the choosed quantity is '5' --> the price for 5 parts is not defined!)
|
||||
* @throws Exception if there was an error
|
||||
* Adds an pricedetail to this orderdetail
|
||||
* @param Pricedetail $pricedetail The pricedetail to add
|
||||
* @return Orderdetail
|
||||
*/
|
||||
public function getPrice(int $quantity = 1, $multiplier = null) : ?float
|
||||
public function addPricedetail(Pricedetail $pricedetail) : Orderdetail
|
||||
{
|
||||
$pricedetail->setOrderdetail($this);
|
||||
$this->pricedetails->add($pricedetail);
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (($quantity === 0) && ($multiplier === null)) {
|
||||
return 0.0;
|
||||
/**
|
||||
* 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 pricedetail for a specific quantity.
|
||||
* @param float $quantity this is the quantity to choose the correct pricedetails
|
||||
*
|
||||
* @return Pricedetail|null: the price as a bcmath string. Null if there are no orderdetails for the given quantity
|
||||
*/
|
||||
public function getPrice(float $quantity = 1) : ?Pricedetail
|
||||
{
|
||||
if ($quantity <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$all_pricedetails = $this->getPricedetails();
|
||||
|
||||
if (count($all_pricedetails) == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
$correct_pricedetails = null;
|
||||
foreach ($all_pricedetails as $pricedetails) {
|
||||
$correct_pricedetail = null;
|
||||
foreach ($all_pricedetails as $pricedetail) {
|
||||
// choose the correct pricedetails for the choosed quantity ($quantity)
|
||||
if ($quantity < $pricedetails->getMinDiscountQuantity()) {
|
||||
if ($quantity < $pricedetail->getMinDiscountQuantity()) {
|
||||
break;
|
||||
}
|
||||
|
||||
$correct_pricedetails = $pricedetails;
|
||||
$correct_pricedetail = $pricedetail;
|
||||
}
|
||||
|
||||
if ($correct_pricedetails === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($multiplier === null) {
|
||||
$multiplier = $quantity;
|
||||
}
|
||||
|
||||
return $correct_pricedetails->getPricePerUnit($multiplier);
|
||||
return $correct_pricedetail;
|
||||
}
|
||||
|
||||
/********************************************************************************
|
||||
|
@ -275,6 +279,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
|
||||
|
@ -321,6 +334,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;
|
||||
|
|
|
@ -62,6 +62,7 @@ declare(strict_types=1);
|
|||
namespace App\Entity\PriceInformations;
|
||||
|
||||
use App\Entity\Base\DBElement;
|
||||
use App\Entity\Base\TimestampTrait;
|
||||
use App\Validator\Constraints\Selectable;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
|
@ -72,23 +73,30 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||
*
|
||||
* @ORM\Entity()
|
||||
* @ORM\Table("`pricedetails`")
|
||||
* @ORM\HasLifecycleCallbacks()
|
||||
* @UniqueEntity(fields={"orderdetail", "min_discount_quantity"})
|
||||
*/
|
||||
class Pricedetail extends DBElement
|
||||
{
|
||||
|
||||
public const PRICE_PRECISION = 5;
|
||||
|
||||
use TimestampTrait;
|
||||
|
||||
/**
|
||||
* @var Orderdetail
|
||||
* @ORM\ManyToOne(targetEntity="Orderdetail", inversedBy="pricedetails")
|
||||
* @ORM\JoinColumn(name="orderdetails_id", referencedColumnName="id")
|
||||
* @Assert\NotNull()
|
||||
*/
|
||||
protected $orderdetail;
|
||||
|
||||
/**
|
||||
* @var float The price related to the detail. (Given in the selected currency)
|
||||
* @var string The price related to the detail. (Given in the selected currency)
|
||||
* @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.
|
||||
|
@ -100,28 +108,29 @@ class Pricedetail extends DBElement
|
|||
protected $currency;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* @ORM\Column(type="integer")
|
||||
* @var float
|
||||
* @ORM\Column(type="float")
|
||||
* @Assert\Positive()
|
||||
*/
|
||||
protected $price_related_quantity;
|
||||
protected $price_related_quantity = 1.0;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* @ORM\Column(type="integer")
|
||||
* @var float
|
||||
* @ORM\Column(type="float")
|
||||
* @Assert\Positive()
|
||||
*/
|
||||
protected $min_discount_quantity;
|
||||
protected $min_discount_quantity = 1.0;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* @ORM\Column(type="boolean")
|
||||
*/
|
||||
protected $manual_input;
|
||||
protected $manual_input = true;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="datetimetz")
|
||||
*/
|
||||
protected $last_modified;
|
||||
public function __construct()
|
||||
{
|
||||
bcscale(static::PRICE_PRECISION);
|
||||
}
|
||||
|
||||
/********************************************************************************
|
||||
*
|
||||
|
@ -144,39 +153,69 @@ class Pricedetail extends DBElement
|
|||
* It is given in current currency and for the price related quantity.
|
||||
* @return float
|
||||
*/
|
||||
public function getPrice() : float
|
||||
public function getPriceFloat() : float
|
||||
{
|
||||
return (float) $this->price;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the price associated with this pricedetail.
|
||||
* It is given in current currency and for the price related quantity.
|
||||
* @return string The price as string, like returned raw from DB.
|
||||
*/
|
||||
public function getPrice() : string
|
||||
{
|
||||
return $this->price;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the price associated with this pricedetail as integer.
|
||||
* It is given in current currency and for the price related quantity, in parts of 0.00001 (5 digits)
|
||||
* @return int
|
||||
*/
|
||||
public function getPriceInt() : int
|
||||
{
|
||||
return (int) str_replace('.', '', $this->price);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the price for a single unit in the currency associated with this price detail.
|
||||
*
|
||||
* @param int $multiplier The returned price (float or string) will be multiplied
|
||||
* @param float|string $multiplier The returned price (float or string) will be multiplied
|
||||
* with this multiplier.
|
||||
*
|
||||
* You will get the price for $multiplier parts. If you want the price which is stored
|
||||
* in the database, you have to pass the "price_related_quantity" count as $multiplier.
|
||||
*
|
||||
* @return float the price as a float number
|
||||
* @return string the price as a bcmath string
|
||||
|
||||
*/
|
||||
public function getPricePerUnit(int $multiplier = 1) : float
|
||||
public function getPricePerUnit($multiplier = 1.0) : string
|
||||
{
|
||||
return ($this->price * $multiplier) / $this->price_related_quantity;
|
||||
$multiplier = (string) $multiplier;
|
||||
$tmp = bcmul($this->price, $multiplier, static::PRICE_PRECISION);
|
||||
return bcdiv($tmp, (string) $this->price_related_quantity, static::PRICE_PRECISION);
|
||||
//return ($this->price * $multiplier) / $this->price_related_quantity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the price related quantity.
|
||||
*
|
||||
* This is the quantity, for which the price is valid.
|
||||
* The amount is measured in part unit.
|
||||
*
|
||||
* @return int the price related quantity
|
||||
* @return float the price related quantity
|
||||
*
|
||||
* @see Pricedetail::setPriceRelatedQuantity()
|
||||
*/
|
||||
public function getPriceRelatedQuantity(): int
|
||||
public function getPriceRelatedQuantity(): float
|
||||
{
|
||||
if ($this->orderdetail && $this->orderdetail->getPart()) {
|
||||
if (!$this->orderdetail->getPart()->useFloatAmount()) {
|
||||
$tmp = round($this->price_related_quantity);
|
||||
return $tmp < 1 ? 1 : $tmp;
|
||||
}
|
||||
}
|
||||
return $this->price_related_quantity;
|
||||
}
|
||||
|
||||
|
@ -186,12 +225,21 @@ class Pricedetail extends DBElement
|
|||
* "Minimum discount quantity" means the minimum order quantity for which the price
|
||||
* of this orderdetails is valid.
|
||||
*
|
||||
* The amount is measured in part unit.
|
||||
*
|
||||
* @return int the minimum discount quantity
|
||||
*
|
||||
* @see Pricedetail::setMinDiscountQuantity()
|
||||
*/
|
||||
public function getMinDiscountQuantity(): int
|
||||
public function getMinDiscountQuantity(): float
|
||||
{
|
||||
if ($this->orderdetail && $this->orderdetail->getPart()) {
|
||||
if (!$this->orderdetail->getPart()->useFloatAmount()) {
|
||||
$tmp = round($this->min_discount_quantity);
|
||||
return $tmp < 1 ? 1 : $tmp;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->min_discount_quantity;
|
||||
}
|
||||
|
||||
|
@ -211,6 +259,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.
|
||||
|
@ -226,7 +285,7 @@ class Pricedetail extends DBElement
|
|||
/**
|
||||
* Set the price.
|
||||
*
|
||||
* @param float $new_price the new price as a float number
|
||||
* @param string $new_price the new price as a float number
|
||||
*
|
||||
* * This is the price for "price_related_quantity" parts!!
|
||||
* * Example: if "price_related_quantity" is '10',
|
||||
|
@ -234,7 +293,7 @@ class Pricedetail extends DBElement
|
|||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setPrice(float $new_price): Pricedetail
|
||||
public function setPrice(string $new_price): Pricedetail
|
||||
{
|
||||
//Assert::natural($new_price, 'The new price must be positive! Got %s!');
|
||||
|
||||
|
@ -256,11 +315,8 @@ class Pricedetail extends DBElement
|
|||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setPriceRelatedQuantity(int $new_price_related_quantity): self
|
||||
public function setPriceRelatedQuantity(float $new_price_related_quantity): self
|
||||
{
|
||||
//Assert::greaterThan($new_price_related_quantity, 0,
|
||||
// 'The new price related quantity must be greater zero! Got %s.');
|
||||
|
||||
$this->price_related_quantity = $new_price_related_quantity;
|
||||
|
||||
return $this;
|
||||
|
@ -285,11 +341,8 @@ class Pricedetail extends DBElement
|
|||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setMinDiscountQuantity(int $new_min_discount_quantity): self
|
||||
public function setMinDiscountQuantity(float $new_min_discount_quantity): self
|
||||
{
|
||||
//Assert::greaterThan($new_min_discount_quantity, 0,
|
||||
// 'The new minimum discount quantity must be greater zero! Got %s.');
|
||||
|
||||
$this->min_discount_quantity = $new_min_discount_quantity;
|
||||
|
||||
return $this;
|
||||
|
@ -303,6 +356,6 @@ class Pricedetail extends DBElement
|
|||
*/
|
||||
public function getIDString(): string
|
||||
{
|
||||
return 'PD'.sprintf('%06d', $this->getID());
|
||||
return 'PD' . sprintf('%06d', $this->getID());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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],
|
||||
|
|
116
src/Form/Part/OrderdetailType.php
Normal file
116
src/Form/Part/OrderdetailType.php
Normal file
|
@ -0,0 +1,116 @@
|
|||
<?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\MeasurementUnit;
|
||||
use App\Entity\Parts\Supplier;
|
||||
use App\Entity\PriceInformations\Orderdetail;
|
||||
use App\Entity\PriceInformations\Pricedetail;
|
||||
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\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use function foo\func;
|
||||
|
||||
class OrderdetailType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
/** @var Orderdetail $orderdetail */
|
||||
$orderdetail = $builder->getData();
|
||||
|
||||
$builder->add('supplierpartnr', TextType::class, [
|
||||
'label' => 'orderdetails.edit.supplierpartnr',
|
||||
'required' => false,
|
||||
'empty_data' => ""
|
||||
]);
|
||||
|
||||
$builder->add('supplier', StructuralEntityType::class, [
|
||||
'class' => Supplier::class, 'disable_not_selectable' => true,
|
||||
'label' => 'orderdetails.edit.supplier'
|
||||
]);
|
||||
|
||||
$builder->add('supplier_product_url', UrlType::class, [
|
||||
'required' => false,
|
||||
'empty_data' => "",
|
||||
'label' => 'orderdetails.edit.url'
|
||||
]);
|
||||
|
||||
$builder->add('obsolete', CheckboxType::class, [
|
||||
'required' => false,
|
||||
'label_attr' => ['class' => 'checkbox-custom'],
|
||||
'label' => 'orderdetails.edit.obsolete'
|
||||
]);
|
||||
|
||||
|
||||
//Add pricedetails after we know the data, so we can set the default currency
|
||||
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($options) {
|
||||
/** @var Orderdetail $orderdetail */
|
||||
$orderdetail = $event->getData();
|
||||
|
||||
$dummy_pricedetail = new Pricedetail();
|
||||
if ($orderdetail->getSupplier() !== null) {
|
||||
$dummy_pricedetail->setCurrency($orderdetail->getSupplier()->getDefaultCurrency());
|
||||
}
|
||||
|
||||
//Attachment section
|
||||
$event->getForm()->add('pricedetails', CollectionType::class, [
|
||||
'entry_type' => PricedetailType::class,
|
||||
'allow_add' => true, 'allow_delete' => true,
|
||||
'label' => false,
|
||||
'prototype_data' => $dummy_pricedetail,
|
||||
'by_reference' => false,
|
||||
'entry_options' => [
|
||||
'measurement_unit' => $options['measurement_unit']
|
||||
]
|
||||
]);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Orderdetail::class,
|
||||
'error_bubbling' => false,
|
||||
]);
|
||||
|
||||
$resolver->setRequired('measurement_unit');
|
||||
$resolver->setAllowedTypes('measurement_unit', [MeasurementUnit::class, 'null']);
|
||||
}
|
||||
}
|
|
@ -38,6 +38,7 @@ use App\Entity\Parts\Manufacturer;
|
|||
use App\Entity\Parts\MeasurementUnit;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Entity\Parts\Storelocation;
|
||||
use App\Entity\PriceInformations\Orderdetail;
|
||||
use App\Form\AttachmentFormType;
|
||||
use App\Form\AttachmentType;
|
||||
use App\Form\Type\SIUnitType;
|
||||
|
@ -144,6 +145,18 @@ 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,
|
||||
'prototype_data' => new Orderdetail(),
|
||||
'entry_options' => [
|
||||
'measurement_unit' => $part->getPartUnit()
|
||||
]
|
||||
]);
|
||||
|
||||
$builder
|
||||
//Buttons
|
||||
->add('save', SubmitType::class, ['label' => 'part.edit.save'])
|
||||
|
|
79
src/Form/Part/PricedetailType.php
Normal file
79
src/Form/Part/PricedetailType.php
Normal file
|
@ -0,0 +1,79 @@
|
|||
<?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\MeasurementUnit;
|
||||
use App\Entity\PriceInformations\Currency;
|
||||
use App\Entity\PriceInformations\Orderdetail;
|
||||
use App\Entity\PriceInformations\Pricedetail;
|
||||
use App\Form\Type\CurrencyEntityType;
|
||||
use App\Form\Type\SIUnitType;
|
||||
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)
|
||||
{
|
||||
//No labels needed, we define translation in templates
|
||||
$builder->add("min_discount_quantity", SIUnitType::class, [
|
||||
'measurement_unit' => $options['measurement_unit'],
|
||||
'attr' => ['class' => 'form-control-sm']
|
||||
]);
|
||||
$builder->add("price_related_quantity", SIUnitType::class, [
|
||||
'measurement_unit' => $options['measurement_unit'],
|
||||
'attr' => ['class' => 'form-control-sm']
|
||||
]);
|
||||
$builder->add("price", NumberType::class, [
|
||||
'scale' => 5,
|
||||
'html5' => true,
|
||||
'attr' => ['min' => 0, 'step' => "any"]
|
||||
]);
|
||||
$builder->add("currency", CurrencyEntityType::class, ['required' => false]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Pricedetail::class,
|
||||
'error_bubbling' => false
|
||||
]);
|
||||
|
||||
$resolver->setRequired('measurement_unit');
|
||||
$resolver->setAllowedTypes('measurement_unit', [MeasurementUnit::class, 'null']);
|
||||
}
|
||||
}
|
118
src/Form/Type/CurrencyEntityType.php
Normal file
118
src/Form/Type/CurrencyEntityType.php
Normal 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(' ', $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;
|
||||
}
|
||||
}
|
|
@ -126,6 +126,13 @@ class SIUnitType extends AbstractType implements DataMapperInterface
|
|||
|
||||
public function buildView(FormView $view, FormInterface $form, array $options)
|
||||
{
|
||||
$view->vars['sm'] = false;
|
||||
|
||||
//Check if we need to make this thing small
|
||||
if (isset($options['attr']['class'])) {
|
||||
$view->vars['sm'] = (strpos($options['attr']['class'], 'form-control-sm') !== false);
|
||||
}
|
||||
|
||||
$view->vars['unit'] = $options['unit'];
|
||||
parent::buildView($view, $form, $options); // TODO: Change the autogenerated stub
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
98
src/Migrations/Version20190829104643.php
Normal file
98
src/Migrations/Version20190829104643.php
Normal file
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20190829104643 extends AbstractMigration
|
||||
{
|
||||
public function getDescription() : string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema) : void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
|
||||
|
||||
//Add timestamps to orderdetails and pricedetails
|
||||
|
||||
$this->addSql('ALTER TABLE attachements CHANGE element_id element_id INT DEFAULT NULL, CHANGE type_id type_id INT DEFAULT NULL, CHANGE last_modified last_modified DATETIME NOT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL');
|
||||
$this->addSql('ALTER TABLE attachement_types CHANGE parent_id parent_id INT DEFAULT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL, CHANGE last_modified last_modified DATETIME NOT NULL');
|
||||
$this->addSql('ALTER TABLE devices CHANGE parent_id parent_id INT DEFAULT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL, CHANGE last_modified last_modified DATETIME NOT NULL');
|
||||
$this->addSql('ALTER TABLE device_parts CHANGE id_part id_part INT DEFAULT NULL, CHANGE id_device id_device INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE categories CHANGE parent_id parent_id INT DEFAULT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL, CHANGE last_modified last_modified DATETIME NOT NULL');
|
||||
$this->addSql('ALTER TABLE footprints CHANGE parent_id parent_id INT DEFAULT NULL, CHANGE filename filename MEDIUMTEXT NOT NULL, CHANGE filename_3d filename_3d MEDIUMTEXT NOT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL, CHANGE last_modified last_modified DATETIME NOT NULL');
|
||||
$this->addSql('ALTER TABLE manufacturers CHANGE parent_id parent_id INT DEFAULT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL, CHANGE last_modified last_modified DATETIME NOT NULL');
|
||||
$this->addSql('ALTER TABLE measurement_units CHANGE unit unit VARCHAR(255) DEFAULT NULL, CHANGE parent_id parent_id INT DEFAULT NULL, CHANGE last_modified last_modified DATETIME NOT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL');
|
||||
$this->addSql('ALTER TABLE measurement_units ADD CONSTRAINT FK_F5AF83CF727ACA70 FOREIGN KEY (parent_id) REFERENCES `measurement_units` (id)');
|
||||
$this->addSql('CREATE INDEX IDX_F5AF83CF727ACA70 ON measurement_units (parent_id)');
|
||||
$this->addSql('ALTER TABLE parts ADD manufacturing_status VARCHAR(255) DEFAULT NULL, CHANGE id_category id_category INT DEFAULT NULL, CHANGE id_footprint id_footprint INT DEFAULT NULL, CHANGE order_orderdetails_id order_orderdetails_id INT DEFAULT NULL, CHANGE id_manufacturer id_manufacturer INT DEFAULT NULL, CHANGE id_master_picture_attachement id_master_picture_attachement INT DEFAULT NULL, CHANGE id_part_unit id_part_unit INT DEFAULT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL, CHANGE last_modified last_modified DATETIME NOT NULL, CHANGE mass mass DOUBLE PRECISION DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE part_lots CHANGE id_store_location id_store_location INT DEFAULT NULL, CHANGE id_part id_part INT DEFAULT NULL, CHANGE expiration_date expiration_date DATETIME DEFAULT NULL, CHANGE last_modified last_modified DATETIME NOT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL');
|
||||
$this->addSql('ALTER TABLE storelocations CHANGE parent_id parent_id INT DEFAULT NULL, CHANGE storage_type_id storage_type_id INT DEFAULT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL, CHANGE last_modified last_modified DATETIME NOT NULL');
|
||||
$this->addSql('ALTER TABLE suppliers CHANGE parent_id parent_id INT DEFAULT NULL, CHANGE default_currency_id default_currency_id INT DEFAULT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL, CHANGE last_modified last_modified DATETIME NOT NULL, CHANGE shipping_costs shipping_costs NUMERIC(11, 5) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE currencies CHANGE exchange_rate exchange_rate NUMERIC(11, 5) DEFAULT NULL, CHANGE parent_id parent_id INT DEFAULT NULL, CHANGE last_modified last_modified DATETIME NOT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL');
|
||||
$this->addSql('ALTER TABLE currencies ADD CONSTRAINT FK_37C44693727ACA70 FOREIGN KEY (parent_id) REFERENCES currencies (id)');
|
||||
$this->addSql('CREATE INDEX IDX_37C44693727ACA70 ON currencies (parent_id)');
|
||||
$this->addSql('ALTER TABLE orderdetails ADD last_modified DATETIME NOT NULL, CHANGE part_id part_id INT DEFAULT NULL, CHANGE id_supplier id_supplier INT DEFAULT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL');
|
||||
$this->addSql('ALTER TABLE pricedetails ADD datetime_added DATETIME NOT NULL, CHANGE orderdetails_id orderdetails_id INT DEFAULT NULL, CHANGE id_currency id_currency INT DEFAULT NULL, CHANGE last_modified last_modified DATETIME NOT NULL');
|
||||
$this->addSql('ALTER TABLE `groups` CHANGE parent_id parent_id INT DEFAULT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL, CHANGE last_modified last_modified DATETIME NOT NULL');
|
||||
$this->addSql('ALTER TABLE users CHANGE group_id group_id INT DEFAULT NULL, CHANGE password password VARCHAR(255) DEFAULT NULL, CHANGE first_name first_name VARCHAR(255) DEFAULT NULL, CHANGE last_name last_name VARCHAR(255) DEFAULT NULL, CHANGE department department VARCHAR(255) DEFAULT NULL, CHANGE email email VARCHAR(255) DEFAULT NULL, CHANGE config_language config_language VARCHAR(255) DEFAULT NULL, CHANGE config_timezone config_timezone VARCHAR(255) DEFAULT NULL, CHANGE config_theme config_theme VARCHAR(255) DEFAULT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL, CHANGE last_modified last_modified DATETIME NOT NULL');
|
||||
|
||||
//Use float values for pricedetails amount, to use with non integer part units
|
||||
$this->addSql('ALTER TABLE pricedetails CHANGE orderdetails_id orderdetails_id INT DEFAULT NULL, CHANGE id_currency id_currency INT DEFAULT NULL, CHANGE price_related_quantity price_related_quantity DOUBLE PRECISION NOT NULL, CHANGE min_discount_quantity min_discount_quantity DOUBLE PRECISION NOT NULL, CHANGE last_modified last_modified DATETIME NOT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL');
|
||||
|
||||
//Fix typo in attachment table names
|
||||
$this->addSql("ALTER TABLE attachements RENAME TO attachments;");
|
||||
$this->addSql("ALTER TABLE attachement_types RENAME TO attachment_types;");
|
||||
|
||||
//Add an filetye filter field to attachment types
|
||||
$this->addSql('ALTER TABLE attachment_types ADD filetype_filter VARCHAR(65535) NOT NULL, CHANGE parent_id parent_id INT DEFAULT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL, CHANGE last_modified last_modified DATETIME NOT NULL');
|
||||
|
||||
|
||||
//Fill empty timestamps with current date
|
||||
$tables = ["attachments", "attachment_types", "categories", "devices", "footprints", "manufacturers",
|
||||
"orderdetails", "pricedetails", "storelocations", "suppliers"];
|
||||
|
||||
foreach ($tables as $table) {
|
||||
$this->addSql("UPDATE $table SET datetime_added = NOW() WHERE datetime_added = '0000-00-00 00:00:00'");
|
||||
$this->addSql("UPDATE $table SET last_modified = datetime_added WHERE last_modified = '0000-00-00 00:00:00'");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function down(Schema $schema) : void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
|
||||
|
||||
$this->addSql('ALTER TABLE `attachement_types` CHANGE parent_id parent_id INT DEFAULT NULL, CHANGE last_modified last_modified DATETIME NOT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL');
|
||||
$this->addSql('ALTER TABLE `attachements` CHANGE type_id type_id INT DEFAULT NULL, CHANGE element_id element_id INT DEFAULT NULL, CHANGE last_modified last_modified DATETIME NOT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL');
|
||||
$this->addSql('ALTER TABLE `categories` CHANGE parent_id parent_id INT DEFAULT NULL, CHANGE last_modified last_modified DATETIME NOT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL');
|
||||
$this->addSql('ALTER TABLE currencies DROP FOREIGN KEY FK_37C44693727ACA70');
|
||||
$this->addSql('DROP INDEX IDX_37C44693727ACA70 ON currencies');
|
||||
$this->addSql('ALTER TABLE currencies CHANGE parent_id parent_id INT DEFAULT NULL, CHANGE exchange_rate exchange_rate NUMERIC(11, 5) DEFAULT \'NULL\', CHANGE last_modified last_modified DATETIME NOT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL');
|
||||
$this->addSql('ALTER TABLE `device_parts` CHANGE id_device id_device INT DEFAULT NULL, CHANGE id_part id_part INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE `devices` CHANGE parent_id parent_id INT DEFAULT NULL, CHANGE last_modified last_modified DATETIME NOT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL');
|
||||
$this->addSql('ALTER TABLE `footprints` CHANGE parent_id parent_id INT DEFAULT NULL, CHANGE filename filename MEDIUMTEXT NOT NULL COLLATE utf8_unicode_ci, CHANGE filename_3d filename_3d MEDIUMTEXT NOT NULL COLLATE utf8_unicode_ci, CHANGE last_modified last_modified DATETIME NOT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL');
|
||||
$this->addSql('ALTER TABLE `groups` CHANGE parent_id parent_id INT DEFAULT NULL, CHANGE last_modified last_modified DATETIME NOT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL');
|
||||
$this->addSql('ALTER TABLE `manufacturers` CHANGE parent_id parent_id INT DEFAULT NULL, CHANGE last_modified last_modified DATETIME NOT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL');
|
||||
$this->addSql('ALTER TABLE `measurement_units` DROP FOREIGN KEY FK_F5AF83CF727ACA70');
|
||||
$this->addSql('DROP INDEX IDX_F5AF83CF727ACA70 ON `measurement_units`');
|
||||
$this->addSql('ALTER TABLE `measurement_units` CHANGE parent_id parent_id INT DEFAULT NULL, CHANGE unit unit VARCHAR(255) DEFAULT \'NULL\' COLLATE utf8mb4_unicode_ci, CHANGE last_modified last_modified DATETIME NOT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL');
|
||||
$this->addSql('ALTER TABLE `orderdetails` DROP last_modified, CHANGE part_id part_id INT DEFAULT NULL, CHANGE id_supplier id_supplier INT DEFAULT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL');
|
||||
$this->addSql('ALTER TABLE part_lots CHANGE id_store_location id_store_location INT DEFAULT NULL, CHANGE id_part id_part INT DEFAULT NULL, CHANGE expiration_date expiration_date DATETIME DEFAULT \'NULL\', CHANGE last_modified last_modified DATETIME NOT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL');
|
||||
$this->addSql('ALTER TABLE `parts` DROP manufacturing_status, CHANGE id_category id_category INT DEFAULT NULL, CHANGE id_footprint id_footprint INT DEFAULT NULL, CHANGE id_manufacturer id_manufacturer INT DEFAULT NULL, CHANGE id_master_picture_attachement id_master_picture_attachement INT DEFAULT NULL, CHANGE order_orderdetails_id order_orderdetails_id INT DEFAULT NULL, CHANGE id_part_unit id_part_unit INT DEFAULT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL, CHANGE last_modified last_modified DATETIME NOT NULL, CHANGE mass mass DOUBLE PRECISION DEFAULT \'NULL\'');
|
||||
$this->addSql('ALTER TABLE `pricedetails` DROP datetime_added, CHANGE orderdetails_id orderdetails_id INT DEFAULT NULL, CHANGE id_currency id_currency INT DEFAULT NULL, CHANGE last_modified last_modified DATETIME NOT NULL');
|
||||
$this->addSql('ALTER TABLE `storelocations` CHANGE parent_id parent_id INT DEFAULT NULL, CHANGE storage_type_id storage_type_id INT DEFAULT NULL, CHANGE last_modified last_modified DATETIME NOT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL');
|
||||
$this->addSql('ALTER TABLE `suppliers` CHANGE parent_id parent_id INT DEFAULT NULL, CHANGE default_currency_id default_currency_id INT DEFAULT NULL, CHANGE shipping_costs shipping_costs NUMERIC(11, 5) DEFAULT \'NULL\', CHANGE last_modified last_modified DATETIME NOT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL');
|
||||
$this->addSql('ALTER TABLE `users` CHANGE group_id group_id INT DEFAULT NULL, CHANGE password password VARCHAR(255) DEFAULT \'NULL\' COLLATE utf8_general_ci, CHANGE first_name first_name VARCHAR(255) DEFAULT \'NULL\' COLLATE utf8_general_ci, CHANGE last_name last_name VARCHAR(255) DEFAULT \'NULL\' COLLATE utf8_general_ci, CHANGE department department VARCHAR(255) DEFAULT \'NULL\' COLLATE utf8_general_ci, CHANGE email email VARCHAR(255) DEFAULT \'NULL\' COLLATE utf8_general_ci, CHANGE config_language config_language VARCHAR(255) DEFAULT \'NULL\' COLLATE utf8_general_ci, CHANGE config_timezone config_timezone VARCHAR(255) DEFAULT \'NULL\' COLLATE utf8_general_ci, CHANGE config_theme config_theme VARCHAR(255) DEFAULT \'NULL\' COLLATE utf8_general_ci, CHANGE last_modified last_modified DATETIME NOT NULL, CHANGE datetime_added datetime_added DATETIME NOT NULL');
|
||||
}
|
||||
}
|
|
@ -32,28 +32,44 @@
|
|||
namespace App\Services;
|
||||
|
||||
|
||||
use Gerardojbaez\Money\Money;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface;
|
||||
use App\Entity\PriceInformations\Currency;
|
||||
use Locale;
|
||||
|
||||
class MoneyFormatter
|
||||
{
|
||||
|
||||
private $params;
|
||||
protected $base_currency;
|
||||
protected $locale;
|
||||
|
||||
public function __construct(ContainerBagInterface $params)
|
||||
public function __construct(string $base_currency)
|
||||
{
|
||||
$this->params = $params;
|
||||
$this->base_currency = $base_currency;
|
||||
$this->locale = Locale::getDefault();
|
||||
}
|
||||
|
||||
public function format($amount, string $currency = "") : string
|
||||
/**
|
||||
* Format the the given value in the given currency
|
||||
* @param string|float $value The value that should be formatted.
|
||||
* @param Currency|null $currency The currency that should be used for formatting. If null the global one is used
|
||||
* @param int $decimals The number of decimals that should be shown.
|
||||
* @param bool $show_all_digits If set to true, all digits are shown, even if they are null.
|
||||
* @return string
|
||||
*/
|
||||
public function format($value, ?Currency $currency = null, $decimals = 5, bool $show_all_digits = false)
|
||||
{
|
||||
if ($currency === "") {
|
||||
$currency = $this->params->get("default_currency");
|
||||
$iso_code = $this->base_currency;
|
||||
if ($currency !== null && !empty($currency->getIsoCode())) {
|
||||
$iso_code = $currency->getIsoCode();
|
||||
}
|
||||
|
||||
$money = new Money($amount, $currency);
|
||||
$number_formatter = new \NumberFormatter($this->locale, \NumberFormatter::CURRENCY);
|
||||
if ($show_all_digits) {
|
||||
$number_formatter->setAttribute(\NumberFormatter::FRACTION_DIGITS, $decimals);
|
||||
} else {
|
||||
$number_formatter->setAttribute(\NumberFormatter::MAX_FRACTION_DIGITS, $decimals);
|
||||
}
|
||||
|
||||
return $money->format();
|
||||
return $number_formatter->formatCurrency((float) $value, $iso_code);
|
||||
}
|
||||
|
||||
}
|
206
src/Services/PricedetailHelper.php
Normal file
206
src/Services/PricedetailHelper.php
Normal file
|
@ -0,0 +1,206 @@
|
|||
<?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\Services;
|
||||
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Entity\PriceInformations\Currency;
|
||||
use App\Entity\PriceInformations\Pricedetail;
|
||||
use Locale;
|
||||
|
||||
class PricedetailHelper
|
||||
{
|
||||
protected $base_currency;
|
||||
protected $locale;
|
||||
|
||||
public function __construct(string $base_currency)
|
||||
{
|
||||
$this->base_currency = $base_currency;
|
||||
$this->locale = Locale::getDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the highest amount, for which you get additional discount.
|
||||
* This function determines the highest min_discount_quantity for the given part.
|
||||
* @param Part $part
|
||||
* @return float|null
|
||||
*/
|
||||
public function getMaxDiscountAmount(Part $part) : ?float
|
||||
{
|
||||
$orderdetails = $part->getOrderdetails(true);
|
||||
|
||||
$max = 0;
|
||||
|
||||
foreach ($orderdetails as $orderdetail) {
|
||||
$pricedetails = $orderdetail->getPricedetails();
|
||||
//The orderdetail must have pricedetails, otherwise this will not work!
|
||||
if (empty($pricedetails)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Pricedetails in orderdetails are ordered by min discount quantity,
|
||||
so our first object is our min order amount for the current orderdetail */
|
||||
$max_amount = $pricedetails->last()->getMinDiscountQuantity();
|
||||
|
||||
if ($max_amount > $max) {
|
||||
$max = $max_amount;
|
||||
}
|
||||
}
|
||||
|
||||
if ($max > 0) {
|
||||
return $max;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the minimum amount of the part that can be ordered
|
||||
* @param Part $part The part for which the minimum order amount should be determined.
|
||||
* @return float
|
||||
*/
|
||||
public function getMinOrderAmount(Part $part) : ?float
|
||||
{
|
||||
$orderdetails = $part->getOrderdetails(true);
|
||||
|
||||
$min = INF;
|
||||
|
||||
foreach ($orderdetails as $orderdetail) {
|
||||
$pricedetails = $orderdetail->getPricedetails();
|
||||
//The orderdetail must have pricedetails, otherwise this will not work!
|
||||
if (count($pricedetails) === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Pricedetails in orderdetails are ordered by min discount quantity,
|
||||
so our first object is our min order amount for the current orderdetail */
|
||||
$min_amount = $pricedetails[0]->getMinDiscountQuantity();
|
||||
|
||||
if ($min_amount < $min) {
|
||||
$min = $min_amount;
|
||||
}
|
||||
}
|
||||
|
||||
if ($min < INF) {
|
||||
return $min;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the average price of a part, when ordering the amount $amount.
|
||||
* @param Part $part The part for which the average price should be calculated.
|
||||
* @param float $amount The order amount for which the average price should be calculated.
|
||||
* If set to null, the mininmum order amount for the part is used.
|
||||
* @param Currency|null $currency The currency in which the average price should be calculated
|
||||
* @return string|null The Average price as bcmath string. Returns null, if it was not possible to calculate the
|
||||
* price for the given
|
||||
*/
|
||||
public function calculateAvgPrice(Part $part, ?float $amount = null, ?Currency $currency = null) : ?string
|
||||
{
|
||||
if ($amount === null) {
|
||||
$amount = $this->getMinOrderAmount($part);
|
||||
}
|
||||
|
||||
if ($amount === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$orderdetails = $part->getOrderdetails(true);
|
||||
|
||||
$avg = "0";
|
||||
$count = 0;
|
||||
|
||||
//Find the price for the amount, for the given
|
||||
foreach ($orderdetails as $orderdetail) {
|
||||
$pricedetail = $orderdetail->getPrice($amount);
|
||||
|
||||
//When we dont have informations about this amount, ignore it
|
||||
if ($pricedetail === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$avg = bcadd($avg, $this->convertMoneyToCurrency($pricedetail->getPricePerUnit(), $pricedetail->getCurrency(), $currency), Pricedetail::PRICE_PRECISION);
|
||||
$count++;
|
||||
}
|
||||
|
||||
if ($count === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return bcdiv($avg, (string) $count, Pricedetail::PRICE_PRECISION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given value in origin currency to the choosen target currency
|
||||
* @param $value float|string The value that should be converted
|
||||
* @param Currency|null $originCurrency The currency the $value is given in.
|
||||
* Set to null, to use global base currency.
|
||||
* @param Currency|null $targetCurrency The target currency, to which $value should be converted.
|
||||
* Set to null, to use global base currency.
|
||||
* @return string|null The value in $targetCurrency given as bcmath string.
|
||||
* Returns null, if it was not possible to convert between both values (e.g. when the exchange rates are missing)
|
||||
*/
|
||||
public function convertMoneyToCurrency($value, ?Currency $originCurrency = null, ?Currency $targetCurrency = null) : ?string
|
||||
{
|
||||
//Skip conversion, if both currencies are same
|
||||
if ($originCurrency === $targetCurrency) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$value = (string) $value;
|
||||
|
||||
//Convert value to base currency
|
||||
$val_base = $value;
|
||||
if ($originCurrency !== null) {
|
||||
//Without an exchange rate we can not calculate the exchange rate
|
||||
if ((float) $originCurrency->getExchangeRate() === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$val_base = bcmul($value, $originCurrency->getExchangeRate(), Pricedetail::PRICE_PRECISION);
|
||||
}
|
||||
|
||||
//Convert value in base currency to target currency
|
||||
$val_target = $val_base;
|
||||
if ($targetCurrency !== null) {
|
||||
//Without an exchange rate we can not calculate the exchange rate
|
||||
if ($targetCurrency->getExchangeRate() === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$val_target = bcmul($val_base, $targetCurrency->getInverseExchangeRate(), Pricedetail::PRICE_PRECISION);
|
||||
}
|
||||
|
||||
return $val_target;
|
||||
}
|
||||
}
|
|
@ -32,11 +32,13 @@ namespace App\Twig;
|
|||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\Base\DBElement;
|
||||
use App\Entity\Parts\MeasurementUnit;
|
||||
use App\Entity\PriceInformations\Currency;
|
||||
use App\Services\AmountFormatter;
|
||||
use App\Services\EntityURLGenerator;
|
||||
use App\Services\MoneyFormatter;
|
||||
use App\Services\SIFormatter;
|
||||
use App\Services\TreeBuilder;
|
||||
use Money\Currencies;
|
||||
use Symfony\Component\Cache\Adapter\AdapterInterface;
|
||||
use Symfony\Component\Serializer\SerializerInterface;
|
||||
use Twig\Extension\AbstractExtension;
|
||||
|
@ -123,14 +125,14 @@ class AppExtension extends AbstractExtension
|
|||
return $item->get();
|
||||
}
|
||||
|
||||
public function formatCurrency($amount, $currency = "")
|
||||
public function formatCurrency($amount, Currency $currency = null, int $decimals = 5)
|
||||
{
|
||||
return $this->moneyFormatter->format($amount, $currency);
|
||||
return $this->moneyFormatter->format($amount, $currency, $decimals);
|
||||
}
|
||||
|
||||
public function siFormat($value, $unit, $decimals = 2)
|
||||
public function siFormat($value, $unit, $decimals = 2, bool $show_all_digits = false)
|
||||
{
|
||||
return $this->siformatter->format($value, $unit, $decimals);
|
||||
return $this->siformatter->format($value, $unit, $decimals, $show_all_digits);
|
||||
}
|
||||
|
||||
public function amountFormat($value, ?MeasurementUnit $unit, array $options = [])
|
||||
|
|
|
@ -117,9 +117,6 @@
|
|||
"./config/packages/fos_ckeditor.yaml"
|
||||
]
|
||||
},
|
||||
"gerardojbaez/money": {
|
||||
"version": "v0.3.1"
|
||||
},
|
||||
"guzzlehttp/guzzle": {
|
||||
"version": "6.3.3"
|
||||
},
|
||||
|
|
|
@ -32,11 +32,11 @@
|
|||
{%- endblock choice_widget_options -%}
|
||||
|
||||
{% block si_unit_widget %}
|
||||
<div class="input-group">
|
||||
<div class="input-group {% if sm %}input-group-sm{% endif %}">
|
||||
{{ form_widget(form.value) }}
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-append {% if sm %}input-group-sm{% endif %}">
|
||||
{% if form.prefix is defined %}
|
||||
{{ form_widget(form.prefix, {'attr': {'class': 'custom-select btn'}}) }}
|
||||
{{ form_widget(form.prefix, {'attr': {'class': 'custom-select'}}) }}
|
||||
{% endif %}
|
||||
{% if unit is not empty %}
|
||||
<label class="input-group-text">{{ unit }}</label>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
{% endset %}
|
||||
|
||||
|
||||
<table class="table table-striped" id="attachments_table" data-prototype="{{ form_widget(form.attachments.vars.prototype)|e('html_attr') }}">
|
||||
<table class="table table-striped table-sm" id="attachments_table" data-prototype="{{ form_widget(form.attachments.vars.prototype)|e('html_attr') }}">
|
||||
<tbody>
|
||||
{% for attachment in form.attachments %}
|
||||
<tr>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
{% endset %}
|
||||
|
||||
|
||||
<table class="table table-striped" id="lots_table" data-prototype="{{ form_widget(form.partLots.vars.prototype)|e('html_attr') }}">
|
||||
<table class="table table-striped table-sm" id="lots_table" data-prototype="{{ form_widget(form.partLots.vars.prototype)|e('html_attr') }}">
|
||||
<tbody>
|
||||
{% for lot in form.partLots %}
|
||||
<tr>
|
||||
|
|
84
templates/Parts/edit/_orderdetails.html.twig
Normal file
84
templates/Parts/edit/_orderdetails.html.twig
Normal file
|
@ -0,0 +1,84 @@
|
|||
{% form_theme form with ['Parts/edit/edit_form_styles.html.twig', "bootstrap_4_layout.html.twig"] %}
|
||||
|
||||
<table class="table table-striped table-sm" 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);
|
||||
|
||||
//Determine the id of the fields we later update
|
||||
var price_related_id = $("input[id$='price_related_quantity_value']", newForm).attr('id');
|
||||
var min_discount_id = $("input[id$='min_discount_quantity_value']", newForm).attr('id');
|
||||
|
||||
console.error(price_related_id);
|
||||
console.error(min_discount_id);
|
||||
|
||||
//Determine the new value for the min_discount_qty
|
||||
var new_min_amount = $("input[id$='min_discount_quantity_value']" , $holder).last().val();
|
||||
new_min_amount = parseInt(new_min_amount) + 1;
|
||||
//Determine the next exponent, 10 -> 100 -> 1000
|
||||
new_min_amount = Math.pow(10, Math.ceil(Math.log10(new_min_amount)));
|
||||
|
||||
$holder.children("tbody").append(newForm);
|
||||
|
||||
// Now really update the values, after they were inserted into dom
|
||||
$("#" + min_discount_id).val(new_min_amount);
|
||||
$("#" + price_related_id).val("1");
|
||||
|
||||
|
||||
//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>
|
62
templates/Parts/edit/edit_form_styles.html.twig
Normal file
62
templates/Parts/edit/edit_form_styles.html.twig
Normal file
|
@ -0,0 +1,62 @@
|
|||
{% block pricedetail_widget %}
|
||||
{% form_theme form.currency 'Form/extendedBootstrap4_layout.html.twig' %}
|
||||
|
||||
<tr>
|
||||
<td>{{ form_widget(form.min_discount_quantity, {'attr': {'class': 'form-control-sm'}}) }} {{ form_errors(form.min_discount_quantity) }}</td>
|
||||
<td>
|
||||
<div class="input-group input-group-sm">
|
||||
{{ form_widget(form.price) }}
|
||||
<div class="input-group-append">
|
||||
{{ form_widget(form.currency, {'attr': {'class': 'selectpicker form-control-sm'}}) }}
|
||||
</div>
|
||||
</div>
|
||||
{{ form_errors(form.price) }}
|
||||
{{ form_errors(form.currency) }}
|
||||
</td>
|
||||
<td>{{ form_widget(form.price_related_quantity, {'attr': {'class': 'form-control-sm'}}) }} {{ form_errors(form.price_related_quantity) }}</td>
|
||||
<td><button type="button" class="btn btn-danger order_btn_delete btn-sm" title="{% trans %}orderdetail.delete{% endtrans %}" onclick="delete_pricedetail_entry(this);">
|
||||
<i class="fas fa-trash-alt fa-fw"></i>
|
||||
</button>
|
||||
{{ form_errors(form) }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endblock %}
|
||||
|
||||
{% block orderdetail_widget %}
|
||||
<tr>
|
||||
<td>
|
||||
{{ form_row(form.supplier, {'attr': {'class': 'form-control-sm'}}) }}
|
||||
{{ form_row(form.supplierpartnr, {'attr': {'class': 'form-control-sm'}}) }}
|
||||
{{ form_row(form.supplier_product_url, {'attr': {'class': 'form-control-sm'}}) }}
|
||||
{{ form_widget(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>{% trans %}pricedetails.edit.min_qty{% endtrans %}</th>
|
||||
<th>{% trans %}pricedetails.edit.price{% endtrans %}</th>
|
||||
<th>{% trans %}pricedetails.edit.price_qty{% endtrans %}</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);" title="{% trans %}orderdetail.delete{% endtrans %}">
|
||||
<i class="fas fa-trash-alt fa-fw"></i>
|
||||
</button>
|
||||
{{ form_errors(form) }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endblock %}
|
|
@ -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>
|
||||
|
@ -81,5 +90,6 @@
|
|||
</div>
|
||||
{{ form_row(form.save) }}
|
||||
{{ form_row(form.reset) }}
|
||||
{{ form_errors(form) }}
|
||||
{{ form_end(form) }}
|
||||
{% endblock %}
|
|
@ -34,9 +34,23 @@
|
|||
<i class="fas fa-microchip fa-fw" ></i>
|
||||
<span class="text-muted">{{ part.footprint.fullPath ?? "-"}}</span>
|
||||
</h6>
|
||||
<h6 title="{% trans %}part.avg_price.label{% endtrans %}">
|
||||
<h6>
|
||||
<i class="fas fa-money-bill-alt fa-fw"></i>
|
||||
<span class="text-muted">{% if part.averagePrice is not null %}{{ part.averagePrice | moneyFormat }}{% else %}-{% endif %}</span>
|
||||
<span class="text-muted">
|
||||
|
||||
{% set min_order_amount = pricedetail_helper.minOrderAmount(part) %}
|
||||
{% set max_order_amount = pricedetail_helper.maxDiscountAmount(part) %}
|
||||
{% set max_order_price = pricedetail_helper.calculateAvgPrice(part, max_order_amount) %}
|
||||
|
||||
{% if max_order_price is not null %}
|
||||
<span title="{% trans %}part.avg_price.label{% endtrans %} {{ max_order_amount | amountFormat(part.partUnit) }}">{{ max_order_price | moneyFormat }}</span>
|
||||
{% if min_order_amount < max_order_amount %}
|
||||
<span> - </span>
|
||||
<span title="{% trans %}part.avg_price.label{% endtrans %} {{ min_order_amount | amountFormat(part.partUnit) }}">{{pricedetail_helper.calculateAvgPrice(part, min_order_amount) | moneyFormat }}</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
</span>
|
||||
</h6>
|
||||
{#
|
||||
{% if part.comment != "" %}
|
||||
|
|
|
@ -25,20 +25,31 @@
|
|||
<tr>
|
||||
<th>{% trans %}part.order.minamount{% endtrans %}</th>
|
||||
<th>{% trans %}part.order.price{% endtrans %}</th>
|
||||
<th>{% trans %}part.order.single_price{% endtrans %}</th>
|
||||
<th>
|
||||
{% trans %}part.order.single_price{% endtrans %}
|
||||
{% if part.partUnit %}
|
||||
/ 1 {{ part.partUnit.unit }}
|
||||
{% endif %}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for detail in order.pricedetails %}
|
||||
<tr>
|
||||
<td>
|
||||
{{ detail.MinDiscountQuantity }}
|
||||
{{ detail.MinDiscountQuantity | amountFormat(part.partUnit) }}
|
||||
</td>
|
||||
<td>
|
||||
{{ detail.Price | moneyFormat }} <i>{% trans %}part.order.price_per{% endtrans %}</i> {{ detail.PriceRelatedQuantity }}
|
||||
{{ detail.price | moneyFormat(detail.currency) }} / {{ detail.PriceRelatedQuantity | amountFormat(part.partUnit) }}
|
||||
{% if detail.currency and pricedetail_helper.convertMoneyToCurrency(detail.price, detail.currency) > 0 %}
|
||||
<span class="text-muted">({{ pricedetail_helper.convertMoneyToCurrency(detail.price, detail.currency) | moneyFormat() }})</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ detail.PricePerUnit | moneyFormat}}
|
||||
{{ detail.PricePerUnit | moneyFormat(detail.currency) }}
|
||||
{% if detail.currency and pricedetail_helper.convertMoneyToCurrency(detail.PricePerUnit, detail.currency) > 0 %}
|
||||
<span class="text-muted">({{ pricedetail_helper.convertMoneyToCurrency(detail.PricePerUnit, detail.currency) | moneyFormat() }})</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
|
@ -1045,7 +1045,7 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
|
|||
</trans-unit>
|
||||
<trans-unit id="cpRdMwo" resname="part.order.price_per">
|
||||
<source>part.order.price_per</source>
|
||||
<target state="translated">Preis pro</target>
|
||||
<target state="translated">pro</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="EKnfKFl" resname="edit.caption_short">
|
||||
<source>edit.caption_short</source>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue