Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
75.34% |
55 / 73 |
|
50.00% |
4 / 8 |
CRAP | |
0.00% |
0 / 1 |
| UnlinkWithInvoiceItemService | |
75.34% |
55 / 73 |
|
50.00% |
4 / 8 |
22.86 | |
0.00% |
0 / 1 |
| unlink | |
70.00% |
14 / 20 |
|
0.00% |
0 / 1 |
3.24 | |||
| processActivetyLog | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
1 | |||
| updateStatusInvoiceItem | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
| updateStatusPurchaseOrder | |
90.91% |
10 / 11 |
|
0.00% |
0 / 1 |
4.01 | |||
| invoiceItemIsFullyLinked | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| linkedInvoiceAmount | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
1 | |||
| convertPurchaseOrderAmountToInvoiceUnit | |
40.00% |
4 / 10 |
|
0.00% |
0 / 1 |
7.46 | |||
| getConversion | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
| 1 | <?php |
| 2 | |
| 3 | namespace Domain\PurchaseOrder\Services; |
| 4 | |
| 5 | use App\Events\ServiceProcessed; |
| 6 | use App\Models\Conversion\Conversion; |
| 7 | use App\Models\Invoice\Invoice; |
| 8 | use App\Models\Invoice\InvoiceItem; |
| 9 | use App\Models\PurchaseOrder\PurchaseOrderInvoiceItem; |
| 10 | use App\Models\PurchaseOrder\PurchaseOrderItem; |
| 11 | use App\Models\UnitMeasurement\UnitMeasurement; |
| 12 | use App\Models\User; |
| 13 | use Domain\Conversion\Enums\ConversionTypeEnum; |
| 14 | use Domain\Invoice\DTOs\StoreInvoiceActivityLogDTO; |
| 15 | use Domain\Invoice\Enums\InvoiceActivetyLogOperationEnum; |
| 16 | use Domain\Invoice\Enums\InvoiceItemStatusEnum; |
| 17 | use Domain\Invoice\Enums\InvoicePurchaseOrderStatusEnum; |
| 18 | use Domain\Invoice\Exceptions\PurchaseOrderInvoiceItemNotFoundException; |
| 19 | use Domain\Invoice\Services\InvoiceActivityLogStoreService; |
| 20 | use Domain\PurchaseOrder\DTOs\UnlinkPurchaseOrderItemInvoiceItemDTO; |
| 21 | use Domain\PurchaseOrder\Enums\PurchaseOrderInvoiceItemStatusEnum; |
| 22 | use Illuminate\Database\Eloquent\Model; |
| 23 | use Illuminate\Support\Facades\DB; |
| 24 | use Throwable; |
| 25 | |
| 26 | readonly class UnlinkWithInvoiceItemService |
| 27 | { |
| 28 | /** |
| 29 | * @throws Throwable |
| 30 | */ |
| 31 | public function unlink(UnlinkPurchaseOrderItemInvoiceItemDTO $unlinkDTO, User $user): void |
| 32 | { |
| 33 | try { |
| 34 | DB::beginTransaction(); |
| 35 | |
| 36 | $purchaseOrderInvoiceItem = PurchaseOrderInvoiceItem::query() |
| 37 | ->where('invoice_item_id', $unlinkDTO->invoice_item->getKey()) |
| 38 | ->where('purchase_order_item_id', $unlinkDTO->purchase_order_item->getKey()) |
| 39 | ->first(); |
| 40 | |
| 41 | if (! $purchaseOrderInvoiceItem) { |
| 42 | throw new PurchaseOrderInvoiceItemNotFoundException( |
| 43 | __('Link of purchase order item and invoice item not found') |
| 44 | ); |
| 45 | } |
| 46 | |
| 47 | // Invoice to check later if it still has items |
| 48 | $invoice = $purchaseOrderInvoiceItem->invoiceItem?->invoice; |
| 49 | |
| 50 | $purchaseOrderInvoiceItemOld = $purchaseOrderInvoiceItem->getAttributes(); |
| 51 | |
| 52 | $purchaseOrderInvoiceItem->delete(); |
| 53 | |
| 54 | $this->processActivetyLog($unlinkDTO->invoice_item->invoice, $user, $purchaseOrderInvoiceItemOld); |
| 55 | $this->updateStatusInvoiceItem($unlinkDTO->invoice_item); |
| 56 | $this->updateStatusPurchaseOrder($invoice); |
| 57 | |
| 58 | DB::commit(); |
| 59 | } catch (Throwable $exception) { |
| 60 | DB::rollBack(); |
| 61 | throw $exception; |
| 62 | } |
| 63 | |
| 64 | ServiceProcessed::dispatch(self::class, $user); |
| 65 | } |
| 66 | |
| 67 | private function processActivetyLog(Invoice $invoice, User $user, array $details) |
| 68 | { |
| 69 | $invoiceActivityLogDTO = new StoreInvoiceActivityLogDTO([ |
| 70 | 'entity_type' => PurchaseOrderInvoiceItem::class, |
| 71 | 'entity_id' => $details['id'], |
| 72 | 'operation' => InvoiceActivetyLogOperationEnum::DELETE, |
| 73 | 'user' => $user, |
| 74 | 'detail' => json_encode($details), |
| 75 | 'observation' => 'Removido o vĂnculo do item da nota fiscal, com o item da ordem de compra.', |
| 76 | ]); |
| 77 | |
| 78 | (new InvoiceActivityLogStoreService)->store($invoiceActivityLogDTO, $invoice, $user); |
| 79 | } |
| 80 | |
| 81 | private function updateStatusInvoiceItem(InvoiceItem $invoiceItem): void |
| 82 | { |
| 83 | $invoiceItem->setAttribute( |
| 84 | 'status', |
| 85 | $this->invoiceItemIsFullyLinked($invoiceItem) ? InvoiceItemStatusEnum::VALIDATED : InvoiceItemStatusEnum::PENDING |
| 86 | ); |
| 87 | $invoiceItem->save(); |
| 88 | } |
| 89 | |
| 90 | private function updateStatusPurchaseOrder(?Invoice $invoice): void |
| 91 | { |
| 92 | if (! $invoice) { |
| 93 | return; |
| 94 | } |
| 95 | |
| 96 | $allItemsLinked = InvoiceItem::query() |
| 97 | ->where('invoice_id', $invoice->getKey()) |
| 98 | ->get() |
| 99 | ->every(fn (Model $invoiceItem) => $invoiceItem instanceof InvoiceItem && $this->invoiceItemIsFullyLinked($invoiceItem)); |
| 100 | |
| 101 | $invoice->setAttribute( |
| 102 | 'purchase_order_status', |
| 103 | $allItemsLinked ? InvoicePurchaseOrderStatusEnum::VALIDATED : InvoicePurchaseOrderStatusEnum::PENDING |
| 104 | ); |
| 105 | $invoice->save(); |
| 106 | } |
| 107 | |
| 108 | private function invoiceItemIsFullyLinked(InvoiceItem $invoiceItem): bool |
| 109 | { |
| 110 | return round($this->linkedInvoiceAmount($invoiceItem), 2) >= round($invoiceItem->getAmount(), 2); |
| 111 | } |
| 112 | |
| 113 | private function linkedInvoiceAmount(InvoiceItem $invoiceItem): float |
| 114 | { |
| 115 | return (float) PurchaseOrderInvoiceItem::query() |
| 116 | ->with('purchaseOrderItem.product.unitMeasurement') |
| 117 | ->where('invoice_item_id', $invoiceItem->getKey()) |
| 118 | ->where('status', PurchaseOrderInvoiceItemStatusEnum::VALIDATED) |
| 119 | ->get() |
| 120 | ->sum(function (PurchaseOrderInvoiceItem $purchaseOrderInvoiceItem) use ($invoiceItem) { |
| 121 | return $this->convertPurchaseOrderAmountToInvoiceUnit( |
| 122 | $invoiceItem, |
| 123 | $purchaseOrderInvoiceItem->purchaseOrderItem, |
| 124 | (float) $purchaseOrderInvoiceItem->getAttribute('amount') |
| 125 | ) ?? 0; |
| 126 | }); |
| 127 | } |
| 128 | |
| 129 | private function convertPurchaseOrderAmountToInvoiceUnit( |
| 130 | InvoiceItem $invoiceItem, |
| 131 | PurchaseOrderItem $purchaseOrderItem, |
| 132 | float $amount |
| 133 | ): ?float { |
| 134 | $invoiceItem->loadMissing('unitMeasurement'); |
| 135 | $purchaseOrderItem->loadMissing('product.unitMeasurement'); |
| 136 | |
| 137 | if ($invoiceItem->unitMeasurement->getKey() === $purchaseOrderItem->product->unitMeasurement->getKey()) { |
| 138 | return $amount; |
| 139 | } |
| 140 | |
| 141 | $conversion = $this->getConversion($invoiceItem->unitMeasurement, $purchaseOrderItem->product->unitMeasurement); |
| 142 | |
| 143 | if (! $conversion) { |
| 144 | return null; |
| 145 | } |
| 146 | |
| 147 | if ($conversion->getAttribute('type') == ConversionTypeEnum::MULTIPLY) { |
| 148 | return $amount / $conversion->getAttribute('value'); |
| 149 | } |
| 150 | |
| 151 | return $amount * $conversion->getAttribute('value'); |
| 152 | } |
| 153 | |
| 154 | private function getConversion(UnitMeasurement $unitMFrom, UnitMeasurement $unitMTo): ?Conversion |
| 155 | { |
| 156 | $conversion = Conversion::query() |
| 157 | ->where('unit_measurement_from_id', $unitMFrom->getKey()) |
| 158 | ->where('unit_measurement_to_id', $unitMTo->getKey()) |
| 159 | ->first(); |
| 160 | |
| 161 | return $conversion instanceof Conversion ? $conversion : null; |
| 162 | } |
| 163 | } |