Issues (257)

src/Entity/ProjectSystem/ProjectBOMEntry.php (1 issue)

Severity
1
<?php
2
/**
3
 * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
4
 *
5
 * Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
6
 *
7
 * This program is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU Affero General Public License as published
9
 * by the Free Software Foundation, either version 3 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU Affero General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Affero General Public License
18
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19
 */
20
21
declare(strict_types=1);
22
23
namespace App\Entity\ProjectSystem;
24
25
use App\Entity\Base\AbstractDBElement;
26
use App\Entity\Base\TimestampTrait;
27
use App\Entity\Parts\Part;
28
use App\Entity\PriceInformations\Currency;
29
use App\Validator\Constraints\BigDecimal\BigDecimalPositive;
30
use App\Validator\Constraints\Selectable;
31
use Brick\Math\BigDecimal;
32
use Doctrine\ORM\Mapping as ORM;
33
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
34
use Symfony\Component\Validator\Constraints as Assert;
35
use Symfony\Component\Validator\Context\ExecutionContextInterface;
36
37
/**
38
 * The ProjectBOMEntry class represents an entry in a project's BOM.
39
 *
40
 * @ORM\Table("project_bom_entries")
41
 * @ORM\HasLifecycleCallbacks()
42
 * @ORM\Entity()
43
 * @UniqueEntity(fields={"part", "project"}, message="project.bom_entry.part_already_in_bom")
44
 * @UniqueEntity(fields={"name", "project"}, message="project.bom_entry.name_already_in_bom", ignoreNull=true)
45
 */
46
class ProjectBOMEntry extends AbstractDBElement
47
{
48
    use TimestampTrait;
49
50
    /**
51
     * @var float
52
     * @ORM\Column(type="float", name="quantity")
53
     * @Assert\Positive()
54
     */
55
    protected float $quantity;
56
57
    /**
58
     * @var string A comma separated list of the names, where this parts should be placed
59
     * @ORM\Column(type="text", name="mountnames")
60
     */
61
    protected string $mountnames;
62
63
    /**
64
     * @var string An optional name describing this BOM entry (useful for non-part entries)
65
     * @ORM\Column(type="string", nullable=true)
66
     * @Assert\Expression(
67
     *     "this.getPart() !== null or this.getName() !== null",
68
     *     message="validator.project.bom_entry.name_or_part_needed"
69
     * )
70
     */
71
    protected ?string $name = null;
72
73
    /**
74
     * @var string An optional comment for this BOM entry
75
     * @ORM\Column(type="text")
76
     */
77
    protected string $comment;
78
79
    /**
80
     * @var Project
81
     * @ORM\ManyToOne(targetEntity="Project", inversedBy="bom_entries")
82
     * @ORM\JoinColumn(name="id_device", referencedColumnName="id")
83
     */
84
    protected ?Project $project = null;
85
86
    /**
87
     * @var Part|null The part associated with this
88
     * @ORM\ManyToOne(targetEntity="App\Entity\Parts\Part", inversedBy="project_bom_entries")
89
     * @ORM\JoinColumn(name="id_part", referencedColumnName="id", nullable=true)
90
     */
91
    protected ?Part $part = null;
92
93
    /**
94
     * @var BigDecimal The price of this non-part BOM entry
95
     * @ORM\Column(type="big_decimal", precision=11, scale=5, nullable=true)
96
     * @Assert\AtLeastOneOf({
97
     *     @BigDecimalPositive(),
98
     *     @Assert\IsNull()
99
     * })
100
     */
101
    protected ?BigDecimal $price;
102
103
    /**
104
     * @var ?Currency The currency for the price of this non-part BOM entry
105
     * @ORM\ManyToOne(targetEntity="App\Entity\PriceInformations\Currency")
106
     * @ORM\JoinColumn(nullable=true)
107
     * @Selectable()
108
     */
109
    protected ?Currency $price_currency = null;
110
111
    public function __construct()
112
    {
113
        $this->price = BigDecimal::zero()->toScale(5);
114
    }
115
116
    /**
117
     * @return float
118
     */
119
    public function getQuantity(): float
120
    {
121
        return $this->quantity;
122
    }
123
124
    /**
125
     * @param  float  $quantity
126
     * @return ProjectBOMEntry
127
     */
128
    public function setQuantity(float $quantity): ProjectBOMEntry
129
    {
130
        $this->quantity = $quantity;
131
        return $this;
132
    }
133
134
    /**
135
     * @return string
136
     */
137
    public function getMountnames(): string
138
    {
139
        return $this->mountnames;
140
    }
141
142
    /**
143
     * @param  string  $mountnames
144
     * @return ProjectBOMEntry
145
     */
146
    public function setMountnames(string $mountnames): ProjectBOMEntry
147
    {
148
        $this->mountnames = $mountnames;
149
        return $this;
150
    }
151
152
    /**
153
     * @return string
154
     */
155
    public function getName(): ?string
156
    {
157
        return $this->name;
158
    }
159
160
    /**
161
     * @param  string  $name
162
     * @return ProjectBOMEntry
163
     */
164
    public function setName(?string $name): ProjectBOMEntry
165
    {
166
        $this->name = $name;
167
        return $this;
168
    }
169
170
    /**
171
     * @return string
172
     */
173
    public function getComment(): string
174
    {
175
        return $this->comment;
176
    }
177
178
    /**
179
     * @param  string  $comment
180
     * @return ProjectBOMEntry
181
     */
182
    public function setComment(string $comment): ProjectBOMEntry
183
    {
184
        $this->comment = $comment;
185
        return $this;
186
    }
187
188
    /**
189
     * @return Project|null
190
     */
191
    public function getProject(): ?Project
192
    {
193
        return $this->project;
194
    }
195
196
    /**
197
     * @param  Project|null  $project
198
     * @return ProjectBOMEntry
199
     */
200
    public function setProject(?Project $project): ProjectBOMEntry
201
    {
202
        $this->project = $project;
203
        return $this;
204
    }
205
206
207
208
    /**
209
     * @return Part|null
210
     */
211
    public function getPart(): ?Part
212
    {
213
        return $this->part;
214
    }
215
216
    /**
217
     * @param  Part|null  $part
218
     * @return ProjectBOMEntry
219
     */
220
    public function setPart(?Part $part): ProjectBOMEntry
221
    {
222
        $this->part = $part;
223
        return $this;
224
    }
225
226
    /**
227
     * Returns the price of this BOM entry, if existing.
228
     * Prices are only valid on non-Part BOM entries.
229
     * @return BigDecimal|null
230
     */
231
    public function getPrice(): ?BigDecimal
232
    {
233
        return $this->price;
234
    }
235
236
    /**
237
     * Sets the price of this BOM entry.
238
     * Prices are only valid on non-Part BOM entries.
239
     * @param  BigDecimal|null  $price
240
     */
241
    public function setPrice(?BigDecimal $price): void
242
    {
243
        $this->price = $price;
244
    }
245
246
    /**
247
     * @return Currency|null
248
     */
249
    public function getPriceCurrency(): ?Currency
250
    {
251
        return $this->price_currency;
252
    }
253
254
    /**
255
     * @param  Currency|null  $price_currency
256
     */
257
    public function setPriceCurrency(?Currency $price_currency): void
258
    {
259
        $this->price_currency = $price_currency;
260
    }
261
262
    /**
263
     * Checks whether this BOM entry is a part associated BOM entry or not.
264
     * @return bool True if this BOM entry is a part associated BOM entry, false otherwise.
265
     */
266
    public function isPartBomEntry(): bool
267
    {
268
        return $this->part !== null;
269
    }
270
271
    /**
272
     * @Assert\Callback
273
     */
274
    public function validate(ExecutionContextInterface $context, $payload): void
0 ignored issues
show
The parameter $payload is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

274
    public function validate(ExecutionContextInterface $context, /** @scrutinizer ignore-unused */ $payload): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
275
    {
276
        //Round quantity to whole numbers, if the part is not a decimal part
277
        if ($this->part) {
278
            if (!$this->part->getPartUnit() || $this->part->getPartUnit()->isInteger()) {
279
                $this->quantity = round($this->quantity);
280
            }
281
        }
282
        //Non-Part BOM entries are rounded
283
        if ($this->part === null) {
284
            $this->quantity = round($this->quantity);
285
        }
286
287
        //Check that every part name in the mountnames list is unique (per bom_entry)
288
        $mountnames = explode(',', $this->mountnames);
289
        $mountnames = array_map('trim', $mountnames);
290
        $uniq_mountnames = array_unique($mountnames);
291
292
        //If the number of unique names is not the same as the number of names, there are duplicates
293
        if (count($mountnames) !== count($uniq_mountnames)) {
294
            $context->buildViolation('project.bom_entry.mountnames_not_unique')
295
                ->atPath('mountnames')
296
                ->addViolation();
297
        }
298
299
        //Check that the number of mountnames is the same as the (rounded) quantity
300
        if (!empty($this->mountnames) && count($uniq_mountnames) !== (int) round ($this->quantity)) {
301
            $context->buildViolation('project.bom_entry.mountnames_quantity_mismatch')
302
                ->atPath('mountnames')
303
                ->addViolation();
304
        }
305
306
        //Prices are only allowed on non-part BOM entries
307
        if ($this->part !== null && $this->price !== null) {
308
            $context->buildViolation('project.bom_entry.price_not_allowed_on_parts')
309
                ->atPath('price')
310
                ->addViolation();
311
        }
312
313
        //Check that the part is not the build representation part of this device or one of its parents
314
        if ($this->part && $this->part->getBuiltProject() !== null) {
315
            //Get the associated project
316
            $associated_project = $this->part->getBuiltProject();
317
            //Check that it is not the same as the current project neither one of its parents
318
            $current_project = $this->project;
319
            while ($current_project) {
320
                if ($associated_project === $current_project) {
321
                    $context->buildViolation('project.bom_entry.can_not_add_own_builds_part')
322
                        ->atPath('part')
323
                        ->addViolation();
324
                }
325
                $current_project = $current_project->getParent();
326
            }
327
        }
328
    }
329
330
331
}
332