Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
78.85% covered (warning)
78.85%
82 / 104
37.50% covered (danger)
37.50%
3 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
PickItemFromAddressService
78.85% covered (warning)
78.85%
82 / 104
37.50% covered (danger)
37.50%
3 / 8
22.42
0.00% covered (danger)
0.00%
0 / 1
 pick
84.21% covered (warning)
84.21%
32 / 38
0.00% covered (danger)
0.00%
0 / 1
4.06
 findAddressItem
66.67% covered (warning)
66.67%
6 / 9
0.00% covered (danger)
0.00%
0 / 1
2.15
 populateItemToTransferDTO
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 validateCanPickedItem
45.00% covered (danger)
45.00%
9 / 20
0.00% covered (danger)
0.00%
0 / 1
9.16
 storePickedItem
93.75% covered (success)
93.75%
15 / 16
0.00% covered (danger)
0.00%
0 / 1
2.00
 markItemAsPicked
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 addToShippingPackaging
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
2.01
 setPickingOrderProductAsSeparated
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace Domain\PickingOrder\Services;
4
5use Exception;
6use Throwable;
7
8use Illuminate\Support\Facades\DB;
9use Illuminate\Support\ItemNotFoundException;
10
11use App\Models\PickedItem;
12use App\Models\User;
13use App\Events\ServiceProcessed;
14use App\Models\Address\Address;
15use App\Models\Item\Item;
16use App\Models\PickingOrder\PickingOrder;
17use App\Models\StockIO\StockIOProduct;
18
19use Domain\PickingOrder\DTOs\PickedItemDTO;
20use Domain\PickingOrder\DTOs\PickItemDTO;
21use Domain\ShippingPackaging\DTOs\ShippingPackagingAddProductDTO;
22use Domain\ShippingPackaging\Exceptions\ShippingPackingException;
23use Domain\ShippingPackaging\Services\ShippingPackagingAddProductService;
24use Domain\Transaction\Enums\TransactionOutputReasonEnum;
25use Domain\Transaction\Enums\TransactionTypeEnum;
26use Domain\Transferable\DTOs\TransferItemDTO;
27use Domain\Transferable\Exceptions\CantTransferItemException;
28use Domain\Transferable\Services\TransferableTransferPickedItemService;
29use Domain\PickingOrder\Exception\PickingOrderCanceledException;
30use Domain\TransferTask\Enums\PickedTransferTaskProductStatusEnum;
31use Domain\PickingOrder\Enums\PickingOrderStatusEnum;
32use Domain\StockIO\Enums\StockProductStatusEnum;
33
34readonly class PickItemFromAddressService
35{
36
37    /**
38     * @param PickItemDTO $pickItemDTO
39     * @param Address $address
40     * @param PickingOrder $pickingOrder
41     * @param User $user
42     * @return void
43     * @throws Throwable
44     */
45    public function pick(PickItemDTO $pickItemDTO, Address $address, PickingOrder $pickingOrder, User $user): void
46    {
47        if ($pickingOrder->isCanceled()) {
48            throw new PickingOrderCanceledException(
49                __('Picking order is canceled.')
50            );
51        }
52
53        try {
54            DB::beginTransaction();
55
56            PickingOrder::where('id', $pickingOrder->getKey())
57                ->lockForUpdate()
58                ->first();
59
60            $itemOnAddress = $this->findAddressItem($address, $pickItemDTO);
61            $this->validateCanPickedItem($itemOnAddress, $pickItemDTO);
62
63            $itemToTransferDTO = $this->populateItemToTransferDTO(
64                $itemOnAddress,
65                $pickItemDTO->amount,
66                $pickItemDTO->readingLogDTOs
67            );
68
69            if ($pickingOrder->isPaused()) {
70                (new PickingOrderStartService())->resumeCurrentPause($pickingOrder);
71
72                $pickingOrder->status = PickingOrderStatusEnum::STARTED;
73                $pickingOrder->save();
74            }
75
76            $stockIO = $pickingOrder->stockIO;
77
78            $item = (new TransferableTransferPickedItemService())->transferOne(
79                $itemToTransferDTO,
80                $address,
81                $stockIO,
82                TransactionTypeEnum::OUTPUT,
83                TransactionOutputReasonEnum::PRE_ORDER->value,
84                $user,
85                $pickItemDTO->observation,
86                $stockIO->sector->value
87            );
88
89            $pickedItem = $this->storePickedItem($pickItemDTO, $item, $user);
90            $this->markItemAsPicked($pickItemDTO->stockIOProduct);
91            $this->addToShippingPackaging($pickItemDTO, $pickingOrder, $pickedItem, $user);
92
93            DB::commit();
94        } catch (Throwable $throwable) {
95            DB::rollBack();
96            throw $throwable;
97        }
98
99        ServiceProcessed::dispatch(self::class, $user);
100    }
101
102
103    /**
104     * @param Address $address
105     * @param PickItemDTO $pickedItemDTO
106     * @return Item
107     */
108    private function findAddressItem(Address $address, PickItemDTO $pickedItemDTO): Item
109    {
110
111        $itemToTransfer = $address->findItemByProductionOrder(
112            $pickedItemDTO->stockIOProduct->product,
113            $pickedItemDTO->integrationItemDTO->productionOrder
114        );
115
116        if (!$itemToTransfer) {
117            throw new ItemNotFoundException(
118                __("Product is not part of this address, verify it's ID and/or it's OP")
119            );
120        }
121
122        return $itemToTransfer;
123    }
124
125    /**
126     * @param Item $itemToTransfer
127     * @param float $amount
128     * @param array $readingLogDTOs
129     * @return TransferItemDTO
130     */
131    private function populateItemToTransferDTO(
132        Item $itemToTransfer,
133        float $amount,
134        array $readingLogDTOs = []
135    ): TransferItemDTO {
136        return new TransferItemDTO([
137            'item'         => $itemToTransfer,
138            'amount'       => $amount,
139            'reading_logs' => $readingLogDTOs,
140        ]);
141    }
142
143    /**
144     * @param Item $addressItem
145     * @param PickItemDTO $pickedItemDTO
146     * @return void
147     */
148    private function validateCanPickedItem(Item $addressItem, PickItemDTO $pickedItemDTO): void
149    {
150        if ($addressItem->isAmountExceeded($pickedItemDTO->amount)) {
151            if($addressItem->pickTransferTaskProducts
152                ->where('status', PickedTransferTaskProductStatusEnum::TRANSFERRING)
153                ->sum('amount') > 0
154            ) {
155                throw new CantTransferItemException(
156                    __('The quantity you are trying to transfer exceeds the available amount. Part of the item is already reserved for another transfer.')
157                );
158            } else {
159                throw new CantTransferItemException(
160                    __('The amount you are trying to transfer exceeds the available amount in the item')
161                );
162            }
163        }
164
165        if ($pickedItemDTO->stockIOProduct->hasAmountExceededLimit($pickedItemDTO->amount)) {
166            throw new CantTransferItemException(
167                __('The quantity entered exceeds the quantity requested by the picking order')
168            );
169        }
170
171        if ($pickedItemDTO->stockIOProduct->getAttribute('status') == StockProductStatusEnum::CANCELED) {
172            throw new CantTransferItemException(
173                __('Cant pick the product: :product. Picking order product is canceled.',
174                    ['product' => $pickedItemDTO->stockIOProduct->product->integration->getAttribute('parameters')['integration_code']]
175                )
176            );
177        }
178    }
179
180    /**
181     * @param PickItemDTO $pickItemDTO
182     * @param Item $item
183     * @param User $user
184     * @return PickedItem
185     * @throws Exception
186     */
187    private function storePickedItem(PickItemDTO $pickItemDTO, Item $item, User $user): PickedItem
188    {
189        $stockIOProduct = $pickItemDTO->stockIOProduct;
190        $pickedItem = PickedItem::select('*')
191            ->where('stock_io_product_id', $stockIOProduct->getKey())
192            ->where('item_id', $item->getKey())->first();
193
194        if ($pickedItem) {
195            return (new PickedItemUpdateAmountService())->update($pickItemDTO, $item, $user);
196        } else {
197            $pickedItemDTO = new PickedItemDTO([
198                'stock_io_product' => $stockIOProduct,
199                'item' => $item,
200                'observation' => $pickItemDTO->observation,
201                'amount' => $pickItemDTO->amount,
202            ]);
203
204            return (new PickedItemStoreService())->store(
205                $pickedItemDTO,
206                $user
207            );
208        }
209    }
210
211    /**
212     * @param StockIOProduct $stockOutputProduct
213     * @return void
214     */
215    private function markItemAsPicked(StockIOProduct $stockOutputProduct): void
216    {
217        if (($stockOutputProduct->pickedItems()->sum('amount') ?? 0) == $stockOutputProduct->getAttribute('amount')) {
218            $stockOutputProduct->setAttribute('picked', true);
219            $stockOutputProduct->setAttribute('status', StockProductStatusEnum::SEPARATED);
220            $stockOutputProduct->save();
221
222            $this->setPickingOrderProductAsSeparated($stockOutputProduct);
223        }
224    }
225
226    /**
227     * @param PickItemDTO $pickItemDTO
228     * @param PickingOrder $pickingOrder
229     * @param User $user
230     * @return void
231     * @throws Exception
232     */
233    private function addToShippingPackaging(
234        PickItemDTO $pickItemDTO,
235        PickingOrder $pickingOrder,
236        PickedItem $pickedItem,
237        User $user
238    ): void {
239        $shippingPackaging = $pickingOrder->shippingPackaging;
240
241        if (!$shippingPackaging) {
242            throw new ShippingPackingException(__('Separation packaging not found in task'));
243        }
244
245        $dto = new ShippingPackagingAddProductDTO([
246            'stock_io_product' => $pickItemDTO->stockIOProduct,
247            'amount' => $pickItemDTO->amount,
248            'production_order' => $pickItemDTO->integrationItemDTO->productionOrder,
249        ]);
250
251        (new ShippingPackagingAddProductService())->execute($dto, $shippingPackaging, $pickedItem, $user);
252    }
253
254    /**
255     * @param StockIOProduct $stockOutputProduct
256     * @return void
257     */
258    private function setPickingOrderProductAsSeparated(StockIOProduct $stockOutputProduct): void
259    {
260        $stockOutputProduct->pickingOrderProduct->setAttribute('status', PickingOrderStatusEnum::SEPARATED);
261        $stockOutputProduct->pickingOrderProduct->save();
262    }
263}