ProjectBuildRequest::getLotWithdrawAmount()   A
last analyzed

Complexity

Conditions 4
Paths 5

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 9
c 0
b 0
f 0
nc 5
nop 1
dl 0
loc 15
rs 9.9666
1
<?php
2
/*
3
 * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
4
 *
5
 *  Copyright (C) 2019 - 2023 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
namespace App\Helpers\Projects;
22
23
use App\Entity\Parts\PartLot;
24
use App\Entity\ProjectSystem\Project;
25
use App\Entity\ProjectSystem\ProjectBOMEntry;
26
use App\Validator\Constraints\ProjectSystem\ValidProjectBuildRequest;
27
28
/**
29
 * @ValidProjectBuildRequest()
30
 */
31
final class ProjectBuildRequest
32
{
33
    private Project $project;
34
    private int $number_of_builds;
35
36
    /**
37
     * @var array<int, float>
38
     */
39
    private array $withdraw_amounts = [];
40
41
    private string $comment = '';
42
43
    private ?PartLot $builds_lot = null;
44
45
    private bool $add_build_to_builds_part = false;
46
47
    /**
48
     * @param  Project  $project  The project that should be build
49
     * @param  int  $number_of_builds The number of builds that should be created
50
     */
51
    public function __construct(Project $project, int $number_of_builds)
52
    {
53
        if ($number_of_builds < 1) {
54
            throw new \InvalidArgumentException('Number of builds must be at least 1!');
55
        }
56
57
        $this->project = $project;
58
        $this->number_of_builds = $number_of_builds;
59
60
        $this->initializeArray();
61
62
        //By default, use the first available lot of builds part if there is one.
63
        if($project->getBuildPart() !== null) {
64
            $this->add_build_to_builds_part = true;
65
            foreach( $project->getBuildPart()->getPartLots() as $lot) {
66
                if (!$lot->isInstockUnknown()) {
67
                    $this->builds_lot = $lot;
68
                    break;
69
                }
70
            }
71
        }
72
    }
73
74
    private function initializeArray(): void
75
    {
76
        //Completely reset the array
77
        $this->withdraw_amounts = [];
78
79
        //Now create an array for each BOM entry
80
        foreach ($this->getPartBomEntries() as $bom_entry) {
81
            $remaining_amount = $this->getNeededAmountForBOMEntry($bom_entry);
82
            foreach($this->getPartLotsForBOMEntry($bom_entry) as $lot) {
83
                //If the lot has instock use it for the build
84
                $this->withdraw_amounts[$lot->getID()] = min($remaining_amount, $lot->getAmount());
85
                $remaining_amount -= max(0, $this->withdraw_amounts[$lot->getID()]);
86
            }
87
        }
88
    }
89
90
    /**
91
     * Ensure that the projectBOMEntry belongs to the project, otherwise throw an exception.
92
     * @param  ProjectBOMEntry  $entry
93
     * @return void
94
     */
95
    private function ensureBOMEntryValid(ProjectBOMEntry $entry): void
96
    {
97
        if ($entry->getProject() !== $this->project) {
98
            throw new \InvalidArgumentException('The given BOM entry does not belong to the project!');
99
        }
100
    }
101
102
    /**
103
     * Returns the partlot where the builds should be added to, or null if it should not be added to any lot.
104
     * @return PartLot|null
105
     */
106
    public function getBuildsPartLot(): ?PartLot
107
    {
108
        return $this->builds_lot;
109
    }
110
111
    /**
112
     * Return if the builds should be added to the builds part of this project as new stock
113
     * @return bool
114
     */
115
    public function getAddBuildsToBuildsPart(): bool
116
    {
117
        return $this->add_build_to_builds_part;
118
    }
119
120
    /**
121
     * Set if the builds should be added to the builds part of this project as new stock
122
     * @param  bool  $new_value
123
     * @return $this
124
     */
125
    public function setAddBuildsToBuildsPart(bool $new_value): self
126
    {
127
        $this->add_build_to_builds_part = $new_value;
128
129
        if ($new_value === false) {
130
            $this->builds_lot = null;
131
        }
132
133
        return $this;
134
    }
135
136
    /**
137
     * Set the partlot where the builds should be added to, or null if it should not be added to any lot.
138
     * The part lot must belong to the project build part, or an exception is thrown!
139
     * @param  PartLot|null  $new_part_lot
140
     * @return $this
141
     */
142
    public function setBuildsPartLot(?PartLot $new_part_lot): self
143
    {
144
        //Ensure that this new_part_lot belongs to the project
145
        if (($new_part_lot !== null && $new_part_lot->getPart() !== $this->project->getBuildPart()) || $this->project->getBuildPart() === null) {
146
            throw new \InvalidArgumentException('The given part lot does not belong to the projects build part!');
147
        }
148
149
        if ($new_part_lot !== null) {
150
            $this->setAddBuildsToBuildsPart(true);
151
        }
152
153
        $this->builds_lot = $new_part_lot;
154
155
        return $this;
156
    }
157
158
    /**
159
     * Returns the comment where the user can write additional information about the build.
160
     * @return string
161
     */
162
    public function getComment(): string
163
    {
164
        return $this->comment;
165
    }
166
167
    /**
168
     * Sets the comment where the user can write additional information about the build.
169
     * @param  string  $comment
170
     */
171
    public function setComment(string $comment): void
172
    {
173
        $this->comment = $comment;
174
    }
175
176
    /**
177
     * Returns the amount of parts that should be withdrawn from the given lot for the corresponding BOM entry.
178
     * @param PartLot|int $lot The part lot (or the ID of the part lot) for which the withdraw amount should be get
179
     * @return float
180
     */
181
    public function getLotWithdrawAmount($lot): float
182
    {
183
        if ($lot instanceof PartLot) {
184
            $lot_id = $lot->getID();
185
        } elseif (is_int($lot)) {
0 ignored issues
show
introduced by
The condition is_int($lot) is always true.
Loading history...
186
            $lot_id = $lot;
187
        } else {
188
            throw new \InvalidArgumentException('The given lot must be an instance of PartLot or an ID of a PartLot!');
189
        }
190
191
        if (! array_key_exists($lot_id, $this->withdraw_amounts)) {
192
            throw new \InvalidArgumentException('The given lot is not in the withdraw amounts array!');
193
        }
194
195
        return $this->withdraw_amounts[$lot_id];
196
    }
197
198
    /**
199
     * Sets the amount of parts that should be withdrawn from the given lot for the corresponding BOM entry.
200
     * @param PartLot|int $lot The part lot (or the ID of the part lot) for which the withdraw amount should be get
201
     * @param  float  $amount
202
     * @return $this
203
     */
204
    public function setLotWithdrawAmount($lot, float $amount): self
205
    {
206
        if ($lot instanceof PartLot) {
207
            $lot_id = $lot->getID();
208
        } elseif (is_int($lot)) {
0 ignored issues
show
introduced by
The condition is_int($lot) is always true.
Loading history...
209
            $lot_id = $lot;
210
        } else {
211
            throw new \InvalidArgumentException('The given lot must be an instance of PartLot or an ID of a PartLot!');
212
        }
213
214
        $this->withdraw_amounts[$lot_id] = $amount;
215
216
        return $this;
217
    }
218
219
    /**
220
     * Returns the sum of all withdraw amounts for the given BOM entry.
221
     * @param  ProjectBOMEntry  $entry
222
     * @return float
223
     */
224
    public function getWithdrawAmountSum(ProjectBOMEntry $entry): float
225
    {
226
        $this->ensureBOMEntryValid($entry);
227
228
        $sum = 0;
229
        foreach ($this->getPartLotsForBOMEntry($entry) as $lot) {
230
            $sum += $this->getLotWithdrawAmount($lot);
231
        }
232
233
        if ($entry->getPart() && !$entry->getPart()->useFloatAmount()) {
234
            $sum = round($sum);
235
        }
236
237
        return $sum;
238
    }
239
240
    /**
241
     * Returns the number of available lots to take stock from for the given BOM entry.
242
     * @param ProjectBOMEntry $projectBOMEntry
243
     * @return PartLot[]|null Returns null if the entry is a non-part BOM entry
244
     */
245
    public function getPartLotsForBOMEntry(ProjectBOMEntry $projectBOMEntry): ?array
246
    {
247
        $this->ensureBOMEntryValid($projectBOMEntry);
248
249
        if ($projectBOMEntry->getPart() === null) {
250
            return null;
251
        }
252
253
        //Filter out all lots which have unknown instock
254
        return $projectBOMEntry->getPart()->getPartLots()->filter(fn (PartLot $lot) => !$lot->isInstockUnknown())->toArray();
255
    }
256
257
    /**
258
     * Returns the needed amount of parts for the given BOM entry.
259
     * @param  ProjectBOMEntry  $entry
260
     * @return float
261
     */
262
    public function getNeededAmountForBOMEntry(ProjectBOMEntry $entry): float
263
    {
264
        $this->ensureBOMEntryValid($entry);
265
266
        return $entry->getQuantity() * $this->number_of_builds;
267
    }
268
269
    /**
270
     * Returns the list of all bom entries that have to be build.
271
     * @return ProjectBOMEntry[]
272
     */
273
    public function getBomEntries(): array
274
    {
275
        return $this->project->getBomEntries()->toArray();
276
    }
277
278
    /**
279
     * Returns the all part bom entries that have to be build.
280
     * @return ProjectBOMEntry[]
281
     */
282
    public function getPartBomEntries(): array
283
    {
284
        return $this->project->getBomEntries()->filter(function (ProjectBOMEntry $entry) {
285
            return $entry->isPartBomEntry();
286
        })->toArray();
287
    }
288
289
    /**
290
     * Returns which project should be build
291
     * @return Project
292
     */
293
    public function getProject(): Project
294
    {
295
        return $this->project;
296
    }
297
298
    /**
299
     * Returns the number of builds that should be created.
300
     * @return int
301
     */
302
    public function getNumberOfBuilds(): int
303
    {
304
        return $this->number_of_builds;
305
    }
306
}