Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
78.95% |
90 / 114 |
|
57.14% |
4 / 7 |
CRAP | |
0.00% |
0 / 1 |
| ItemAdjustmentService | |
78.95% |
90 / 114 |
|
57.14% |
4 / 7 |
25.11 | |
0.00% |
0 / 1 |
| adjust | |
79.31% |
46 / 58 |
|
0.00% |
0 / 1 |
9.72 | |||
| decrement | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
1 | |||
| increment | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
1 | |||
| fetchItem | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
| calculateDifference | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| previousValidate | |
25.00% |
1 / 4 |
|
0.00% |
0 / 1 |
6.80 | |||
| validateAmount | |
47.06% |
8 / 17 |
|
0.00% |
0 / 1 |
8.71 | |||
| 1 | <?php |
| 2 | |
| 3 | namespace Domain\Item\Services; |
| 4 | |
| 5 | use Exception; |
| 6 | use Throwable; |
| 7 | use function abs; |
| 8 | |
| 9 | use Illuminate\Database\Eloquent\Builder; |
| 10 | use Illuminate\Support\Facades\DB; |
| 11 | |
| 12 | use App\Models\Item\Item; |
| 13 | use App\Models\User; |
| 14 | |
| 15 | use Domain\Item\DTOs\ItemAdjustmentDTO; |
| 16 | use Domain\Item\DTOs\ItemsAdjustmentDTO; |
| 17 | use Domain\Item\Interfaces\HasItemsInterface; |
| 18 | use Domain\Shared\Enums\SectorEnum; |
| 19 | use Domain\StockIO\Enums\StockItemAdjustmentReasonEnum; |
| 20 | use Domain\Transaction\Enums\TransactionTypeEnum; |
| 21 | use Domain\Transferable\DTOs\TransferItemDTO; |
| 22 | use Domain\Transferable\Exceptions\TransferableException; |
| 23 | use Domain\Transferable\Services\TransferableTransferItemService; |
| 24 | use Domain\Integration\Services\IntegrationStoreService; |
| 25 | use Domain\Integration\DTOs\IntegrationItemDTO; |
| 26 | use Domain\Transferable\Exceptions\CantTransferItemException; |
| 27 | use Domain\TransferTask\Enums\PickedTransferTaskProductStatusEnum; |
| 28 | |
| 29 | readonly 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 | } |