Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
46.08% covered (danger)
46.08%
47 / 102
12.50% covered (danger)
12.50%
1 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
StockAdjustmentService
46.08% covered (danger)
46.08%
47 / 102
12.50% covered (danger)
12.50%
1 / 8
75.60
0.00% covered (danger)
0.00%
0 / 1
 storeItem
88.00% covered (warning)
88.00%
22 / 25
0.00% covered (danger)
0.00%
0 / 1
2.01
 editItem
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
2
 calculateDifference
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 deleteItem
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 validations
23.53% covered (danger)
23.53%
4 / 17
0.00% covered (danger)
0.00%
0 / 1
36.62
 validateAmount
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
 validateSameItem
70.00% covered (warning)
70.00%
7 / 10
0.00% covered (danger)
0.00%
0 / 1
2.11
 storeTransaction
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace Domain\StockAdjustment\Services;
4
5use App\Helper\AppHelper;
6use App\Models\Address\Address;
7use App\Models\Item\Item;
8use App\Models\Product\Product;
9use App\Models\User;
10use Domain\Integration\DTOs\IntegrationItemWarehouseDTO;
11use Domain\Integration\Services\IntegrationStoreService;
12use Domain\Item\Interfaces\HasItemsInterface;
13use Domain\Shared\Enums\SectorEnum;
14use Domain\StockAdjustment\DTOs\StockAdjustmentDeleteItemDTO;
15use Domain\StockAdjustment\DTOs\StockAdjustmentEditItemDTO;
16use Domain\StockAdjustment\DTOs\StockAdjustmentStoreItemDTO;
17use Domain\Transaction\Enums\TransactionTransferReasonEnum;
18use Domain\Transaction\Enums\TransactionTypeEnum;
19use Domain\Transferable\DTOs\TransferItemDTO;
20use Domain\Transferable\Exceptions\TransferableException;
21use Domain\Transferable\Services\TransferableTransferItemService;
22use Illuminate\Support\Facades\DB;
23
24readonly class StockAdjustmentService
25{
26    private User $user;
27    private Address $address;
28    private Item $item;
29
30    /**
31     * @param StockAdjustmentStoreItemDTO $adjustmentStoreItemDTO
32     * @param User $user
33     * @return Item
34     * @throws \Throwable
35     */
36    public function storeItem(StockAdjustmentStoreItemDTO $adjustmentStoreItemDTO, User $user): Item
37    {
38        $this->address = $adjustmentStoreItemDTO->address;
39        $this->user = $user;
40        $product = $adjustmentStoreItemDTO->product;
41
42        $this->validations();
43        $this->validateSameItem($adjustmentStoreItemDTO, $product);
44
45        try {
46            DB::beginTransaction();
47
48            /** @var Item $item */
49            $item = $this->address->items()->newModelInstance();
50            $item->product()->associate($product);
51            $item->incrementAmount($adjustmentStoreItemDTO->amount);
52
53            //Salva o item do address através da relation
54            $this->address->items()->save($item);
55
56            //Gera integration do item
57            $integrationItemDTO = new IntegrationItemWarehouseDTO([
58                'priority_field' => AppHelper::getPriorityField($product)?->value,
59                'priority_value'    => $adjustmentStoreItemDTO->priorityValue ?? null,
60            ]);
61
62            (new IntegrationStoreService())->store($integrationItemDTO, $item, $user);
63
64            //Cria nova transação
65            $this->storeTransaction($item, $adjustmentStoreItemDTO->amount, $this->address, $adjustmentStoreItemDTO->observation, false);
66
67            DB::commit();
68
69            return $item->load([
70                'product.integration',
71                'integration',
72                'itemable.transferable',
73            ]);
74
75        } catch (\Throwable $exception) {
76            DB::rollBack();
77            throw $exception;
78        }
79
80    }
81
82    /**
83     * @param StockAdjustmentEditItemDTO $adjustmentEditItemDTO
84     * @param Item $item
85     * @param User $user
86     * @return void
87     * @throws \Throwable
88     */
89    public function editItem(StockAdjustmentEditItemDTO $adjustmentEditItemDTO, Item $item, User $user): void
90    {
91        $this->address = $adjustmentEditItemDTO->address;
92        $this->user = $user;
93        $this->item = $item;
94
95        $this->validations();
96        $this->validateAmount($adjustmentEditItemDTO);
97
98        $difference = $this->calculateDifference($adjustmentEditItemDTO, $this->item->getAttribute('amount'));
99
100        //Cria nova transação com o valor do item a ser ajustado sendo a diferença entre os amounts
101        $this->storeTransaction(
102            $item,
103            $difference,
104            $this->address,
105            $adjustmentEditItemDTO->observation
106        );
107    }
108
109    /**
110     * @param StockAdjustmentEditItemDTO $adjustmentEditItemDTO
111     * @param float $amount
112     * @return float
113     */
114    public function calculateDifference(StockAdjustmentEditItemDTO $adjustmentEditItemDTO, float $amount): float
115    {
116        return round($adjustmentEditItemDTO->amount - $amount, 2);
117    }
118
119    /**
120     * @param StockAdjustmentDeleteItemDTO $adjustmentDeleteItemDTO
121     * @param Item $item
122     * @param User $user
123     * @return void
124     * @throws \Throwable
125     */
126    public function deleteItem(StockAdjustmentDeleteItemDTO $adjustmentDeleteItemDTO, Item $item, User $user): void
127    {
128        $this->address = $adjustmentDeleteItemDTO->address;
129        $this->user = $user;
130        $this->item = $item;
131
132        $this->validations();
133
134        //Cria nova transação com o valor total do item negativo
135        $this->storeTransaction(
136            $item,
137            $item->getAttribute('amount') * -1,
138            $this->address,
139            $adjustmentDeleteItemDTO->observation
140        );
141    }
142
143    /**
144     * @return void
145     */
146    public function validations(): void
147    {
148        if ($this->address->sector === SectorEnum::NONE) {
149            throw new TransferableException(
150                __('The entered address does not have a sector')
151            );
152        }
153
154        if ($this->address->sector->value !== $this->user->currentSector()) {
155            throw new TransferableException(
156                __('The entered address does not match your sector')
157            );
158        }
159
160        if (!$this->address->canRemove() || !$this->address->canStore()) {
161            throw new TransferableException(
162                __('The sender address cannot send or receive items')
163            );
164        }
165
166        if (isset($this->item)) {
167            if ($this->item->itemable_id !== $this->address->id || $this->item->itemable_type !== Address::class) {
168                throw new TransferableException(
169                    __('The item does not belong to the informed address')
170                );
171            }
172        }
173    }
174
175    /**
176     * @param StockAdjustmentEditItemDTO $adjustmentEditItemDTO
177     * @return void
178     */
179    public function validateAmount(StockAdjustmentEditItemDTO $adjustmentEditItemDTO): void
180    {
181        $fetchedItem = Item::with([
182            'product.integration'
183        ])->find($this->item->getKey());
184
185        if($fetchedItem) {
186            if ($fetchedItem->getAttribute('amount') !== $adjustmentEditItemDTO->oldAmount) {
187                throw new TransferableException(
188                    __('Product: :product, priority value :priorityValue, amount is outdated. The amount was updated by another proccess',
189                        [
190                            'product' => $fetchedItem->product->integration->getAttribute('parameters')['integration_product'],
191                            'priorityValue' => $fetchedItem->integration->getAttribute('parameters')['priority_value']
192                        ]
193                    )
194                );
195            }
196        }
197    }
198
199    /**
200     * @param StockAdjustmentStoreItemDTO $adjustmentStoreItemDTO
201     * @param Product $product
202     * @return void
203     */
204    public function validateSameItem(StockAdjustmentStoreItemDTO $adjustmentStoreItemDTO, Product $product): void
205    {
206        $itemExists = $this->address->items()
207            ->where('product_id', $product->getKey())
208            ->whereHas('integration', function ($query) use ($adjustmentStoreItemDTO) {
209                $query->where('parameters->priority_value', $adjustmentStoreItemDTO->priorityValue);
210            })
211            ->exists();
212
213        if ($itemExists) {
214            throw new TransferableException(
215                __('An item with the same product and priority already exists at this address.')
216            );
217        }
218    }
219
220    /**
221     * @param Item $item
222     * @param float $amount
223     * @param HasItemsInterface $hasItems
224     * @param $observation
225     * @param bool $performRecalculation
226     * @return void
227     * @throws \Exception
228     */
229    public function storeTransaction(Item $item, float $amount, HasItemsInterface $hasItems, $observation, bool $performRecalculation = true): void
230    {
231        $transferableItemDTO = new TransferItemDTO([
232            'item'   => $item,
233            'amount' => $amount,
234        ]);
235
236        (new TransferableTransferItemService())->transactionOne(
237            $transferableItemDTO,
238            $hasItems,
239            TransactionTypeEnum::ADJUSTMENT,
240            TransactionTransferReasonEnum::REALLOCATION->value,
241            $this->user,
242            $hasItems->sector->value,
243            $observation,
244            $performRecalculation
245        );
246    }
247}