Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
78.95% covered (warning)
78.95%
90 / 114
57.14% covered (warning)
57.14%
4 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
ItemAdjustmentService
78.95% covered (warning)
78.95%
90 / 114
57.14% covered (warning)
57.14%
4 / 7
25.11
0.00% covered (danger)
0.00%
0 / 1
 adjust
79.31% covered (warning)
79.31%
46 / 58
0.00% covered (danger)
0.00%
0 / 1
9.72
 decrement
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
1
 increment
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
1
 fetchItem
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 calculateDifference
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 previousValidate
25.00% covered (danger)
25.00%
1 / 4
0.00% covered (danger)
0.00%
0 / 1
6.80
 validateAmount
47.06% covered (danger)
47.06%
8 / 17
0.00% covered (danger)
0.00%
0 / 1
8.71
1<?php
2
3namespace Domain\Item\Services;
4
5use Exception;
6use Throwable;
7use function abs;
8
9use Illuminate\Database\Eloquent\Builder;
10use Illuminate\Support\Facades\DB;
11
12use App\Models\Item\Item;
13use App\Models\User;
14
15use Domain\Item\DTOs\ItemAdjustmentDTO;
16use Domain\Item\DTOs\ItemsAdjustmentDTO;
17use Domain\Item\Interfaces\HasItemsInterface;
18use Domain\Shared\Enums\SectorEnum;
19use Domain\StockIO\Enums\StockItemAdjustmentReasonEnum;
20use Domain\Transaction\Enums\TransactionTypeEnum;
21use Domain\Transferable\DTOs\TransferItemDTO;
22use Domain\Transferable\Exceptions\TransferableException;
23use Domain\Transferable\Services\TransferableTransferItemService;
24use Domain\Integration\Services\IntegrationStoreService;
25use Domain\Integration\DTOs\IntegrationItemDTO;
26use Domain\Transferable\Exceptions\CantTransferItemException;
27use Domain\TransferTask\Enums\PickedTransferTaskProductStatusEnum;
28
29readonly class ItemAdjustmentService
30{
31    private User $user;
32
33    /**
34     * @param ItemsAdjustmentDTO $itemsAdjustmentDTO
35     * @param HasItemsInterface $hasItems
36     * @param User $user
37     * @return array<Item>
38     * @throws Throwable
39     */
40    public function adjust(ItemsAdjustmentDTO $itemsAdjustmentDTO, HasItemsInterface $hasItems, User $user): array
41    {
42        if($hasItems->sector == SectorEnum::NONE){
43            throw new TransferableException(
44                __("The entered address does not have a sector")
45            );
46        }
47
48        $userSector = $user->currentSector();
49
50        if($hasItems->sector->value != $userSector){
51            throw new TransferableException(
52                __("The entered address does not match your sector")
53            );
54        }
55
56        $this->previousValidate($hasItems);
57        $this->validateAmount($itemsAdjustmentDTO, $hasItems);
58
59        $this->user = $user;
60        $createdItems = [];
61        try {
62            DB::beginTransaction();
63
64            foreach ($itemsAdjustmentDTO->items as $itemAdjustmentDTO) {
65                $itemTarget = $this->fetchItem($itemAdjustmentDTO, $hasItems);
66                $difference = $this->calculateDifference($itemAdjustmentDTO, $itemTarget?->getAttribute('amount') ?? 0);
67
68                if ($difference < 0) {
69                    $this->decrement(
70                        $itemTarget,
71                        $difference,
72                        $hasItems,
73                        $itemsAdjustmentDTO->reason,
74                        $itemsAdjustmentDTO->observation,
75                    );
76                }
77
78                if ($difference > 0) {
79                    if($itemAdjustmentDTO->productionOrder > 0) {
80                        if (!$itemTarget) {
81                            /** @var Item $itemRecipient */
82                            $itemRecipient = $hasItems->items()->newModelInstance();
83
84                            $itemRecipient->product()->associate($itemAdjustmentDTO->product);
85
86                            $itemRecipient->incrementAmount(0);
87
88                            $hasItems->items()->save($itemRecipient);
89
90                            // build integration data
91                            $integrationItemDTO = new IntegrationItemDTO([
92                                'production_order' => $itemAdjustmentDTO->productionOrder,
93                                'movement_date'    => $itemAdjustmentDTO->movementDate ?? now(),
94                            ]);
95
96                            // store integration
97                            (new IntegrationStoreService())->store($integrationItemDTO, $itemRecipient, $user);
98
99                            $itemTarget = $itemRecipient;
100                            $createdItems[] = $itemRecipient;
101                        }
102
103                        $this->increment(
104                            $itemTarget,
105                            $difference,
106                            $hasItems,
107                            $itemsAdjustmentDTO->reason,
108                            $itemsAdjustmentDTO->observation,
109                        );
110                    } else {
111                        throw new TransferableException(
112                            __("It is not allowed to add quantity to the product with OP '0'.")
113                        );
114                    }
115                }
116            }
117
118            DB::commit();
119
120            return array_map(static fn (Item $item) => $item->load([
121                'product.integration',
122                'integration',
123                'itemable.transferable',
124            ]), $createdItems);
125        } catch (Throwable $throwable) {
126            DB::rollBack();
127            throw $throwable;
128        }
129    }
130
131    /**
132     * @param Item $itemTarget
133     * @param float $difference
134     * @param HasItemsInterface $hasItems
135     * @param StockItemAdjustmentReasonEnum $reason
136     * @param string|null $observation
137     * @return void
138     * @throws Exception
139     */
140    private function decrement(
141        Item $itemTarget,
142        float $difference,
143        HasItemsInterface $hasItems,
144        StockItemAdjustmentReasonEnum $reason,
145        ?string $observation = null
146    ): void {
147        $transferableItemDTO = new TransferItemDTO([
148            'item'   => $itemTarget,
149            'amount' => $difference,
150        ]);
151
152        (new TransferableTransferItemService())->transactionOne(
153            $transferableItemDTO,
154            $hasItems,
155            TransactionTypeEnum::ADJUSTMENT,
156            $reason->value,
157            $this->user,
158            $hasItems->sector->value,
159            $observation,
160        );
161    }
162
163    /**
164     * @param Item $itemTarget
165     * @param float $difference
166     * @param HasItemsInterface $hasItems
167     * @param StockItemAdjustmentReasonEnum $reason
168     * @param string|null $observation
169     * @return void
170     * @throws Throwable
171     */
172    private function increment(
173        Item $itemTarget,
174        float $difference,
175        HasItemsInterface $hasItems,
176        StockItemAdjustmentReasonEnum $reason,
177        ?string $observation = null
178    ): void {
179        $quantity = abs($difference);
180
181        $itemTarget = new TransferItemDTO([
182            'item'   => $itemTarget,
183            'amount' => $quantity,
184        ]);
185
186        (new TransferableTransferItemService())->transactionOne(
187            $itemTarget,
188            $hasItems,
189            TransactionTypeEnum::ADJUSTMENT,
190            $reason->value,
191            $this->user,
192            $hasItems->sector->value,
193            $observation,
194        );
195    }
196
197    /**
198     * @param ItemAdjustmentDTO $itemsAdjustmentDTO
199     * @param HasItemsInterface $hasItems
200     * @return Item|null
201     */
202    private function fetchItem(ItemAdjustmentDTO $itemsAdjustmentDTO, HasItemsInterface $hasItems): ?Item
203    {
204        /** @var Item|null $item */
205        $item = $hasItems->items()
206            ->whereHas('integration', function (Builder $builder) use ($itemsAdjustmentDTO) {
207                $builder->where('parameters->production_order', $itemsAdjustmentDTO->productionOrder);
208            })
209            ->where('product_id', $itemsAdjustmentDTO->product->getKey())
210            ->first();
211
212        return $item;
213    }
214
215    /**
216     * @param ItemAdjustmentDTO $itemsAdjustmentDTO
217     * @param float $amount
218     * @return float
219     */
220    private function calculateDifference(ItemAdjustmentDTO $itemsAdjustmentDTO, float $amount): float
221    {
222        return round($itemsAdjustmentDTO->amount - $amount, 2);
223    }
224
225    /**
226     * @param HasItemsInterface $hasItems
227     * @return void
228     */
229    public function previousValidate(HasItemsInterface $hasItems): void
230    {
231        if (!$hasItems->canRemove() || !$hasItems->canStore()) {
232            throw new TransferableException(
233                __('The sender cannot send or receive items')
234            );
235        }
236    }
237
238    /**
239     * @param ItemsAdjustmentDTO $itemsAdjustmentDTO
240     */
241    private function validateAmount(ItemsAdjustmentDTO $itemsAdjustmentDTO, HasItemsInterface $hasItems): void
242    {
243        foreach($itemsAdjustmentDTO->items as $item){
244            $targetItem = $this->fetchItem($item, $hasItems);
245
246            if($targetItem){
247                if($targetItem->getAttribute('amount') <> $item->old_amount){
248                    throw new TransferableException(
249                        __('Product: :product, OP: :OP, amount is outdated. The amount was updated by another proccess',
250                           ['product' => $item->product->integration->getAttribute('parameters')['integration_code'],
251                            'OP'      => $item->productionOrder]
252                        )
253                    );
254                }
255
256                if($targetItem
257                    ->pickTransferTaskProducts
258                    ->where('status', PickedTransferTaskProductStatusEnum::TRANSFERRING)
259                    ->sum('amount') > $item->amount
260                ) {
261                    throw new CantTransferItemException(
262                        __('This adjustment cannot be made, part of the item is reserved for a transfer')
263                    );
264                }
265            }
266        }
267    }
268}