diff --git a/src/Entity/Parts/Part.php b/src/Entity/Parts/Part.php index e3921fd0..16568d61 100644 --- a/src/Entity/Parts/Part.php +++ b/src/Entity/Parts/Part.php @@ -183,15 +183,6 @@ class Part extends AttachmentContainingDBElement */ protected $partLots; - /** - * @var int - * @ORM\Column(type="integer") - * @Assert\GreaterThanOrEqual(0) - * - * @ColumnSecurity(prefix="mininstock", type="integer") - */ - protected $mininstock = 0; - /** * @var float * @ORM\Column(type="float") @@ -199,7 +190,7 @@ class Part extends AttachmentContainingDBElement * * @ColumnSecurity(prefix="mininstock", type="integer") */ - protected $minamount; + protected $minamount = 0; /** * @var string @@ -306,9 +297,9 @@ class Part extends AttachmentContainingDBElement * * @return int count of parts which must be in stock at least */ - public function getMinInstock(): int + public function getMinAmount(): float { - return $this->mininstock; + return $this->minamount; } /** @@ -687,6 +678,45 @@ class Part extends AttachmentContainingDBElement return $this; } + /** + * Checks if this part uses the float amount . + * This setting is based on the part unit (see MeasurementUnit->isInteger()). + * @return bool True if the float amount field should be used. False if the integer instock field should be used. + */ + public function useFloatAmount(): bool + { + if ($this->partUnit instanceof MeasurementUnit) { + return $this->partUnit->isInteger(); + } + + //When no part unit is set, treat it as part count, and so use the integer value. + return false; + } + + /** + * Returns the summed amount of this part (over all part lots) + * @return float + */ + public function getAmountSum() : float + { + //TODO: Find a method to do this natively in SQL, the current method could be a bit slow + $sum = 0; + foreach($this->getPartLots() as $lot) { + //Dont use the instock value, if it is unkown + if ($lot->isInstockUnknown()) { + continue; + } + + $sum += $lot->getAmount(); + } + + if(!$this->useFloatAmount()) { + return $sum; + } + + return round($sum); + } + /******************************************************************************** * * Setters @@ -708,17 +738,18 @@ class Part extends AttachmentContainingDBElement } /** - * Set the count of parts which should be in stock at least. + * Set the minimum amount of parts that have to be instock. + * See getPartUnit() for the associated unit. * * @param int $new_mininstock the new count of parts which should be in stock at least * * @return self */ - public function setMinInstock(int $new_mininstock): self + public function setMinAmount(float $new_mininstock): self { //Assert::natural($new_mininstock, 'The new minimum instock value must be positive! Got %s.'); - $this->mininstock = $new_mininstock; + $this->minamount = $new_minamount; return $this; } diff --git a/src/Entity/Parts/PartLot.php b/src/Entity/Parts/PartLot.php index 83626d79..636ac1e8 100644 --- a/src/Entity/Parts/PartLot.php +++ b/src/Entity/Parts/PartLot.php @@ -65,7 +65,7 @@ class PartLot extends DBElement protected $comment; /** - * @var \DateTime Set a time until when the lot must be used. + * @var ?\DateTime Set a time until when the lot must be used. * Set to null, if the lot can be used indefinitley. * @ORM\Column(type="datetimetz", name="expiration_date", nullable=true) */ @@ -92,16 +92,11 @@ class PartLot extends DBElement */ protected $instock_unknown; - /** - * @var int For integer sizes the instock is saved here. - * @ORM\Column(type="integer", nullable=true) - * @Assert\Positive() - */ - protected $instock; /** * @var float For continuos sizes (length, volume, etc.) the instock is saved here. - * @ORM\Column(type="float", nullable=true) + * @ORM\Column(type="float") + * @Assert\Positive() */ protected $amount; @@ -122,4 +117,179 @@ class PartLot extends DBElement { return 'PL' . $this->getID(); } + + /** + * Check if the current part lot is expired. + * This is the case, if the expiration date is greater the the current date. + * @return bool|null True, if the part lot is expired. Returns null, if no expiration date was set. + * @throws \Exception + */ + public function isExpired(): ?bool + { + if ($this->expiration_date == null) { + return null; + } + + //Check if the expiration date is bigger then current time + return $this->expiration_date < new \DateTime(); + } + + /** + * Gets the description of the part lot. Similar to a "name" of the part lot. + * @return string + */ + public function getDescription(): string + { + return $this->description; + } + + /** + * Sets the description of the part lot. + * @param string $description + * @return PartLot + */ + public function setDescription(string $description): PartLot + { + $this->description = $description; + return $this; + } + + /** + * Gets the comment for this part lot. + * @return string + */ + public function getComment(): string + { + return $this->comment; + } + + /** + * Sets the comment for this part lot. + * @param string $comment + * @return PartLot + */ + public function setComment(string $comment): PartLot + { + $this->comment = $comment; + return $this; + } + + /** + * Gets the expiration date for the part lot. Returns null, if no expiration date was set. + * @return \DateTime|null + */ + public function getExpirationDate(): ?\DateTime + { + return $this->expiration_date; + } + + /** + * Sets the expiration date for the part lot. Set to null, if the part lot does not expires. + * @param \DateTime $expiration_date + * @return PartLot + */ + public function setExpirationDate(?\DateTime $expiration_date): PartLot + { + $this->expiration_date = $expiration_date; + return $this; + } + + /** + * Gets the storage locatiion, where this part lot is stored. + * @return Storelocation The store location where this part is stored + */ + public function getStorageLocation(): Storelocation + { + return $this->storage_location; + } + + /** + * Sets the storage location, where this part lot is stored + * @param Storelocation $storage_location + * @return PartLot + */ + public function setStorageLocation(Storelocation $storage_location): PartLot + { + $this->storage_location = $storage_location; + return $this; + } + + /** + * Return the part that is stored in this part lot. + * @return Part + */ + public function getPart(): Part + { + return $this->part; + } + + /** + * Sets the part that is stored in this part lot. + * @param Part $part + * @return PartLot + */ + public function setPart(Part $part): PartLot + { + $this->part = $part; + return $this; + } + + /** + * Checks if the instock value in the part lot is unknown. + * + * @return bool + */ + public function isInstockUnknown(): bool + { + return $this->instock_unknown; + } + + /** + * Set the unknown instock status of this part lot. + * @param bool $instock_unknown + * @return PartLot + */ + public function setInstockUnknown(bool $instock_unknown): PartLot + { + $this->instock_unknown = $instock_unknown; + return $this; + } + + /** + * @return float + */ + public function getAmount(): float + { + if (!$this->part->useFloatAmount()) { + return round($this->amount); + } + return (float) $this->amount; + } + + public function setAmount(float $new_amount): PartLot + { + $this->amount = $new_amount; + } + + + + /** + * @return bool + */ + public function isNeedsRefill(): bool + { + return $this->needs_refill; + } + + /** + * @param bool $needs_refill + * @return PartLot + */ + public function setNeedsRefill(bool $needs_refill): PartLot + { + $this->needs_refill = $needs_refill; + return $this; + } + + } \ No newline at end of file diff --git a/src/Entity/Parts/Storelocation.php b/src/Entity/Parts/Storelocation.php index f0aee50b..7303b1af 100644 --- a/src/Entity/Parts/Storelocation.php +++ b/src/Entity/Parts/Storelocation.php @@ -62,6 +62,8 @@ declare(strict_types=1); namespace App\Entity\Parts; use App\Entity\Base\PartsContainingDBElement; +use App\Entity\Base\StructuralDBElement; +use App\Form\Type\StructuralEntityType; use Doctrine\ORM\Mapping as ORM; /** @@ -70,7 +72,7 @@ use Doctrine\ORM\Mapping as ORM; * @ORM\Entity(repositoryClass="App\Repository\StructuralDBElementRepository") * @ORM\Table("`storelocations`") */ -class Storelocation extends PartsContainingDBElement +class Storelocation extends StructuralDBElement { /** * @ORM\OneToMany(targetEntity="Storelocation", mappedBy="parent") @@ -83,11 +85,6 @@ class Storelocation extends PartsContainingDBElement */ protected $parent; - /** - * @ORM\OneToMany(targetEntity="Part", mappedBy="storelocation") - */ - protected $parts; - /** * @var bool * @ORM\Column(type="boolean") diff --git a/src/Migrations/Version20190812154222.php b/src/Migrations/Version20190812154222.php index 0356e07c..dfe2f63f 100644 --- a/src/Migrations/Version20190812154222.php +++ b/src/Migrations/Version20190812154222.php @@ -23,22 +23,22 @@ final class Version20190812154222 extends AbstractMigration $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.'); $this->addSql('CREATE TABLE `measurement_units` (id INT AUTO_INCREMENT NOT NULL, unit VARCHAR(255) DEFAULT NULL, is_integer TINYINT(1) NOT NULL, use_si_prefix TINYINT(1) NOT NULL, comment LONGTEXT NOT NULL, parent_id INT DEFAULT NULL, not_selectable TINYINT(1) NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME NOT NULL, datetime_added DATETIME NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB'); - $this->addSql('CREATE TABLE part_lots (id INT AUTO_INCREMENT NOT NULL, id_store_location INT DEFAULT NULL, id_part INT DEFAULT NULL, description LONGTEXT NOT NULL, comment LONGTEXT NOT NULL, expiration_date DATETIME DEFAULT NULL, instock_unknown TINYINT(1) NOT NULL, instock INT DEFAULT NULL, amount DOUBLE PRECISION DEFAULT NULL, needs_refill TINYINT(1) NOT NULL, last_modified DATETIME NOT NULL, datetime_added DATETIME NOT NULL, INDEX IDX_EBC8F9435D8F4B37 (id_store_location), INDEX IDX_EBC8F943C22F6CC4 (id_part), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB'); + $this->addSql('CREATE TABLE part_lots (id INT AUTO_INCREMENT NOT NULL, id_store_location INT DEFAULT NULL, id_part INT DEFAULT NULL, description LONGTEXT NOT NULL, comment LONGTEXT NOT NULL, expiration_date DATETIME DEFAULT NULL, instock_unknown TINYINT(1) NOT NULL, amount DOUBLE PRECISION NOT NULL, needs_refill TINYINT(1) NOT NULL, last_modified DATETIME NOT NULL, datetime_added DATETIME NOT NULL, INDEX IDX_EBC8F9435D8F4B37 (id_store_location), INDEX IDX_EBC8F943C22F6CC4 (id_part), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB'); $this->addSql('CREATE TABLE currencies (id INT AUTO_INCREMENT NOT NULL, iso_code VARCHAR(255) NOT NULL, exchange_rate NUMERIC(11, 5) DEFAULT NULL, comment LONGTEXT NOT NULL, parent_id INT DEFAULT NULL, not_selectable TINYINT(1) NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME NOT NULL, datetime_added DATETIME NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB'); $this->addSql('ALTER TABLE part_lots ADD CONSTRAINT FK_EBC8F9435D8F4B37 FOREIGN KEY (id_store_location) REFERENCES `storelocations` (id)'); $this->addSql('ALTER TABLE part_lots ADD CONSTRAINT FK_EBC8F943C22F6CC4 FOREIGN KEY (id_part) REFERENCES `parts` (id)'); /** Migrate the part locations for parts with known instock */ $this->addSql( - 'INSERT INTO part_lots (id_part, id_store_location, instock, instock_unknown, last_modified, datetime_added) ' . + 'INSERT INTO part_lots (id_part, id_store_location, amount, instock_unknown, last_modified, datetime_added) ' . 'SELECT parts.id, parts.id_storelocation, parts.instock, 0, NOW(), NOW() FROM parts ' . 'WHERE parts.instock >= 0 AND parts.id_storelocation IS NOT NULL' ); //Migrate part locations for parts with unknown instock $this->addSql( - 'INSERT INTO part_lots (id_part, id_store_location, instock, instock_unknown, last_modified, datetime_added) ' . - 'SELECT parts.id, parts.id_storelocation, 0, 1, NOW(), NOW() FROM parts ' . + 'INSERT INTO part_lots (id_part, id_store_location, amount, instock_unknown, last_modified, datetime_added) ' . + 'SELECT parts.id, parts.id_storelocation, 0, 1, NOW(), NOW() FROM parts ' . 'WHERE parts.instock = -2 AND parts.id_storelocation IS NOT NULL' ); @@ -56,7 +56,7 @@ final class Version20190812154222 extends AbstractMigration $this->addSql('CREATE INDEX IDX_AC28B95CECD792C0 ON suppliers (default_currency_id)'); $this->addSql('ALTER TABLE parts DROP FOREIGN KEY parts_id_storelocation_fk'); $this->addSql('DROP INDEX IDX_6940A7FE8DF69834 ON parts'); - $this->addSql('ALTER TABLE parts ADD id_part_unit INT DEFAULT NULL, ADD minamount DOUBLE PRECISION NOT NULL, ADD manufacturer_product_number VARCHAR(255) NOT NULL, ADD needs_review TINYINT(1) NOT NULL, ADD tags LONGTEXT NOT NULL, ADD mass DOUBLE PRECISION DEFAULT NULL, DROP id_storelocation, DROP instock, 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 datetime_added datetime_added DATETIME NOT NULL, CHANGE last_modified last_modified DATETIME NOT NULL'); + $this->addSql('ALTER TABLE parts ADD id_part_unit INT DEFAULT NULL, CHANGE mininstock minamount DOUBLE PRECISION NOT NULL, ADD manufacturer_product_number VARCHAR(255) NOT NULL, ADD needs_review TINYINT(1) NOT NULL, ADD tags LONGTEXT NOT NULL, ADD mass DOUBLE PRECISION DEFAULT NULL, DROP id_storelocation, DROP instock, 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 datetime_added datetime_added DATETIME NOT NULL, CHANGE last_modified last_modified DATETIME NOT NULL'); $this->addSql('ALTER TABLE parts ADD CONSTRAINT FK_6940A7FE2626CEF9 FOREIGN KEY (id_part_unit) REFERENCES `measurement_units` (id)'); $this->addSql('CREATE INDEX IDX_6940A7FE2626CEF9 ON parts (id_part_unit)'); $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'); diff --git a/templates/Parts/info/_main_infos.html.twig b/templates/Parts/info/_main_infos.html.twig index 9ff52e4d..63f4f0da 100644 --- a/templates/Parts/info/_main_infos.html.twig +++ b/templates/Parts/info/_main_infos.html.twig @@ -24,10 +24,10 @@ {{ part.storelocation.fullPath ?? "-"}} #}
{% trans %}part_lots.description{% endtrans %} | +{% trans %}part_lots.storage_location{% endtrans %} | +{% trans %}part_lots.amount{% endtrans %} | +{# Tags row #} + | + |
---|---|---|---|---|
{{ lot.description }} | ++ {{ lot.storageLocation.fullPath }} + | ++ {% if lot.instockUnknown %} + + {% trans %}part_lots.instock_unknown{% endtrans %} + + {% else %} + {{ lot.amount }} + {% endif %} + | +
+
+ {% if lot.expirationDate %}
+
+ {{ lot.expirationDate | localizeddate }}
+
+ {% endif %}
+ {% if lot.expired %}
+
+ |
+