<?php

declare(strict_types=1);

/**
 * 本文件属于 DBErp 进销存系统。
 *
 * 版权所有 © 2026 北京珑大钜商科技有限公司。保留所有权利。
 *
 * 未经官方授权，不得删除、篡改或隐藏本文件中的版权声明、产品标识与来源信息。
 * 商业授权、官方更新与技术支持服务，请以珑大官方支持中心记录为准。
 *
 * @link https://support.loongdom.cn
 * @see  https://www.dberp.com.cn
 */

namespace App\Business\Purchase;

use App\Business\Purchase\Enums\PurchaseConversionDocumentType;
use App\Business\Purchase\Enums\PurchaseInquiryQuoteStatus;
use App\Business\Purchase\Enums\PurchaseOrderStatus;
use App\Business\Purchase\Enums\PurchaseRequestStatus;
use App\Models\DberpPurchaseConversionLine;
use App\Models\DberpPurchaseInquiry;
use App\Models\DberpPurchaseInquiryItem;
use App\Models\DberpPurchaseInquiryQuote;
use App\Models\DberpPurchaseOrder;
use App\Models\DberpPurchaseOrderItem;
use App\Models\DberpPurchaseRequest;
use App\Models\DberpPurchaseRequestItem;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

/**
 * 采购协同来源单据：可选来源查询、转单占用、超额校验与冲回。
 */
final class PurchaseSourceDocumentService
{
    public function __construct(
        private readonly PurchaseRequestService $purchaseRequestService,
    ) {}

    /**
     * @param  array<string, mixed>  $filters
     * @return Builder<DberpPurchaseRequestItem>
     */
    public function selectablePurchaseRequestItemsQuery(array $filters = []): Builder
    {
        $query = DberpPurchaseRequestItem::query()
            ->with(['request', 'product', 'productSku', 'warehouse'])
            ->whereHas('request', function (Builder $requestQuery): void {
                $requestQuery->whereIn('status', $this->convertiblePurchaseRequestStatuses());
            })
            ->where('remaining_quantity', '>', 0);

        if (isset($filters['purchase_request_id'])) {
            $query->where('request_id', (int) $filters['purchase_request_id']);
        }

        if (isset($filters['product_id'])) {
            $query->where('product_id', (int) $filters['product_id']);
        }

        return $query->orderBy('request_id')->orderBy('sort_order')->orderBy('id');
    }

    /**
     * @param  array<string, mixed>  $filters
     * @return Builder<DberpPurchaseInquiryQuote>
     */
    public function selectableInquiryQuotesQuery(array $filters = []): Builder
    {
        $query = DberpPurchaseInquiryQuote::query()
            ->with(['purchaseInquiryItem', 'purchaseInquiry', 'supplier', 'product', 'purchaseRequestItem'])
            ->where('status', PurchaseInquiryQuoteStatus::Active->value)
            ->where(function (Builder $inner): void {
                $inner
                    ->whereNull('valid_until')
                    ->orWhereDate('valid_until', '>=', now()->toDateString());
            });

        if (isset($filters['purchase_request_id'])) {
            $query->where('purchase_request_id', (int) $filters['purchase_request_id']);
        }

        if (isset($filters['purchase_request_item_id'])) {
            $query->where('purchase_request_item_id', (int) $filters['purchase_request_item_id']);
        }

        if (isset($filters['purchase_inquiry_id'])) {
            $query->where('purchase_inquiry_id', (int) $filters['purchase_inquiry_id']);
        }

        if (isset($filters['purchase_inquiry_item_id'])) {
            $query->where('purchase_inquiry_item_id', (int) $filters['purchase_inquiry_item_id']);
        }

        if (isset($filters['supplier_id'])) {
            $query->where('supplier_id', (int) $filters['supplier_id']);
        }

        if (isset($filters['is_recommended'])) {
            $query->where('is_recommended', (bool) $filters['is_recommended']);
        }

        return $query->orderByDesc('quote_date')->orderByDesc('id');
    }

    /**
     * 为已有采购申请明细确保存在询价明细承载（供报价录入等场景保持模型一致）。
     */
    public function ensureInquiryItemForRequestItem(DberpPurchaseRequestItem $requestItem): DberpPurchaseInquiryItem
    {
        $requestItem->loadMissing(['request', 'product']);

        $existing = DberpPurchaseInquiryItem::query()
            ->where('purchase_request_item_id', $requestItem->getKey())
            ->whereHas('inquiry', function (Builder $inquiryQuery) use ($requestItem): void {
                $inquiryQuery
                    ->where('purchase_request_id', $requestItem->request_id)
                    ->whereIn('status', ['draft', 'open']);
            })
            ->first();

        if ($existing instanceof DberpPurchaseInquiryItem) {
            return $existing;
        }

        $inquiry = DberpPurchaseInquiry::query()->create([
            'purchase_request_id' => $requestItem->request_id,
            'purchase_request_code' => $requestItem->request?->code,
            'inquiry_date' => now()->toDateString(),
            'handler_employee_id' => $requestItem->request?->applicant_employee_id,
            'status' => 'open',
            'remark' => $requestItem->request?->remark,
        ]);

        return $inquiry->items()->create([
            'purchase_request_item_id' => $requestItem->getKey(),
            'product_id' => $requestItem->product_id,
            'product_sku_id' => $requestItem->product_sku_id,
            'product_sku_code' => $requestItem->product_sku_code,
            'product_sku_name' => $requestItem->product_sku_name,
            'product_sku_spec' => $requestItem->product_sku_spec,
            'unit_id' => $requestItem->unit_id,
            'unit_name' => $requestItem->unit_name,
            'base_unit_id' => $requestItem->base_unit_id,
            'auxiliary_unit_id' => $requestItem->auxiliary_unit_id,
            'is_auxiliary_unit' => $requestItem->is_auxiliary_unit,
            'auxiliary_unit_quantity' => $requestItem->auxiliary_unit_quantity,
            'base_unit_quantity' => $requestItem->base_unit_quantity,
            'warehouse_id' => $requestItem->warehouse_id,
            'inquiry_quantity' => $requestItem->request_quantity,
            'sort_order' => $requestItem->sort_order,
            'remark' => $requestItem->remark,
        ]);
    }

    public function assertPurchaseRequestAllowsDownstreamProcessing(DberpPurchaseRequest $request): void
    {
        if (! in_array($request->status, $this->convertiblePurchaseRequestStatuses(), true)) {
            throw ValidationException::withMessages([
                'status' => __('dberp.errors.purchase_request_not_convertible'),
            ]);
        }
    }

    public function assertPurchaseRequestItemConvertible(DberpPurchaseRequestItem $item, float $quantity): void
    {
        $item->loadMissing('request');

        if ($item->request === null) {
            throw ValidationException::withMessages([
                'purchase_request_item_id' => __('dberp.errors.invalid_purchase_source_document'),
            ]);
        }

        $this->assertPurchaseRequestAllowsDownstreamProcessing($item->request);

        if ($quantity <= 0) {
            throw ValidationException::withMessages([
                'quantity' => __('dberp.errors.purchase_request_no_convertible_items'),
            ]);
        }

        $remaining = $this->calculatePurchaseRequestItemRemainingQuantity($item);

        if ($remaining <= 0.00005) {
            throw ValidationException::withMessages([
                'purchase_request_item_id' => __('dberp.errors.purchase_request_item_remaining_quantity_insufficient'),
            ]);
        }

        if ($quantity - $remaining > 0.00005) {
            throw ValidationException::withMessages([
                'quantity' => __('dberp.errors.over_conversion'),
            ]);
        }
    }

    public function assertPurchaseRequestItemInquirable(DberpPurchaseRequestItem $item, float $inquiryQuantity): void
    {
        $item->loadMissing('request');

        if ($item->request === null) {
            throw ValidationException::withMessages([
                'purchase_request_item_id' => __('dberp.errors.invalid_purchase_source_document'),
            ]);
        }

        $this->assertPurchaseRequestAllowsDownstreamProcessing($item->request);

        if ($inquiryQuantity <= 0) {
            throw ValidationException::withMessages([
                'inquiry_quantity' => __('dberp.errors.purchase_request_no_inquiry_lines'),
            ]);
        }

        $remaining = $this->calculatePurchaseRequestItemRemainingQuantity($item);

        if ($remaining <= 0.00005) {
            throw ValidationException::withMessages([
                'purchase_request_item_id' => __('dberp.errors.purchase_request_item_remaining_quantity_insufficient'),
            ]);
        }

        if ($inquiryQuantity - $remaining > 0.00005) {
            throw ValidationException::withMessages([
                'inquiry_quantity' => __('dberp.errors.purchase_request_inquiry_quantity_exceeds_remaining'),
            ]);
        }
    }

    public function assertInquiryQuoteConvertible(DberpPurchaseInquiryQuote $quote, float $quantity): void
    {
        $this->assertInquiryQuoteSelectable($quote, (int) $quote->supplier_id);

        if ($quantity <= 0) {
            throw ValidationException::withMessages([
                'quantity' => __('dberp.errors.purchase_request_no_convertible_items'),
            ]);
        }

        if ($this->activeLedgerForInquiryQuote((int) $quote->getKey()) !== null) {
            throw ValidationException::withMessages([
                'source_inquiry_quote_id' => __('dberp.errors.inquiry_quote_already_converted'),
            ]);
        }

        $quote->loadMissing('purchaseRequestItem');

        if ($quote->purchaseRequestItem === null) {
            throw ValidationException::withMessages([
                'source_inquiry_quote_id' => __('dberp.errors.invalid_purchase_source_document'),
            ]);
        }

        $this->assertPurchaseRequestItemConvertible($quote->purchaseRequestItem, $quantity);
    }

    public function assertInquiryQuoteSelectable(DberpPurchaseInquiryQuote $quote, int $supplierId): void
    {
        if ($quote->status === PurchaseInquiryQuoteStatus::Invalidated->value) {
            throw ValidationException::withMessages([
                'source_inquiry_quote_id' => __('dberp.errors.inquiry_quote_invalidated'),
            ]);
        }

        if ($quote->status === PurchaseInquiryQuoteStatus::Expired->value) {
            throw ValidationException::withMessages([
                'source_inquiry_quote_id' => __('dberp.errors.inquiry_quote_expired'),
            ]);
        }

        if (! $quote->isUsable()) {
            throw ValidationException::withMessages([
                'source_inquiry_quote_id' => __('dberp.errors.inquiry_quote_not_selectable'),
            ]);
        }

        if ($quote->valid_until !== null && now()->toDateString() > $quote->valid_until->toDateString()) {
            throw ValidationException::withMessages([
                'source_inquiry_quote_id' => __('dberp.errors.inquiry_quote_expired'),
            ]);
        }

        if ((int) $quote->supplier_id !== $supplierId) {
            throw ValidationException::withMessages([
                'supplier_id' => __('dberp.errors.purchase_order_source_supplier_mismatch'),
            ]);
        }

        if ($quote->purchase_inquiry_item_id === null || $quote->purchase_request_item_id === null) {
            throw ValidationException::withMessages([
                'source_inquiry_quote_id' => __('dberp.errors.invalid_purchase_source_document'),
            ]);
        }
    }

    /**
     * @return array{
     *     purchase_request_item: DberpPurchaseRequestItem,
     *     purchase_inquiry_quote: ?DberpPurchaseInquiryQuote,
     *     purchase_inquiry_id: ?int,
     *     purchase_inquiry_item_id: ?int,
     *     source_type: string,
     *     source_header_id: int,
     *     source_line_id: int
     * }
     */
    public function resolveInquiryQuoteSource(DberpPurchaseInquiryQuote $quote): array
    {
        $quote->loadMissing(['purchaseRequestItem.request', 'purchaseInquiryItem']);

        $requestItem = $quote->purchaseRequestItem;

        if ($requestItem === null) {
            throw ValidationException::withMessages([
                'source_inquiry_quote_id' => __('dberp.errors.invalid_purchase_source_document'),
            ]);
        }

        return [
            'purchase_request_item' => $requestItem,
            'purchase_inquiry_quote' => $quote,
            'purchase_inquiry_id' => $quote->purchase_inquiry_id !== null ? (int) $quote->purchase_inquiry_id : null,
            'purchase_inquiry_item_id' => $quote->purchase_inquiry_item_id !== null ? (int) $quote->purchase_inquiry_item_id : null,
            'source_type' => PurchaseConversionDocumentType::PurchaseInquiryQuote->value,
            'source_header_id' => (int) $quote->purchase_inquiry_id,
            'source_line_id' => (int) $quote->getKey(),
        ];
    }

    public function calculatePurchaseRequestItemRemainingQuantity(DberpPurchaseRequestItem $item): float
    {
        $fresh = DberpPurchaseRequestItem::query()->whereKey($item->getKey())->value('remaining_quantity');

        return (float) ($fresh ?? $item->remaining_quantity ?? 0);
    }

    public function occupySourceForPurchaseOrderItem(DberpPurchaseOrder $order, DberpPurchaseOrderItem $orderItem): void
    {
        if (! $this->orderItemHasSource($orderItem)) {
            return;
        }

        if ($order->status === PurchaseOrderStatus::Voided->value) {
            return;
        }

        $quantity = (float) $orderItem->quantity;

        if ($quantity <= 0) {
            return;
        }

        $resolved = $this->resolveSourceForPurchaseOrderItem($order, $orderItem);

        $this->assertPurchaseRequestItemConvertible($resolved['purchase_request_item'], $quantity);

        if ($resolved['purchase_inquiry_quote'] instanceof DberpPurchaseInquiryQuote) {
            $this->assertInquiryQuoteSelectable($resolved['purchase_inquiry_quote'], (int) $order->supplier_id);
        }

        if ($this->activeLedgerForPurchaseOrderItem($orderItem) !== null) {
            throw ValidationException::withMessages([
                'purchase_order_item_id' => __('dberp.errors.duplicate_conversion'),
            ]);
        }

        DB::transaction(function () use ($order, $orderItem, $resolved, $quantity): void {
            $requestItem = DberpPurchaseRequestItem::query()
                ->whereKey($resolved['purchase_request_item']->getKey())
                ->lockForUpdate()
                ->firstOrFail();

            $this->assertPurchaseRequestItemConvertible($requestItem, $quantity);

            $this->incrementRequestItemConvertedQuantity($requestItem, $quantity);

            $this->createConversionLedger($order, $orderItem, $requestItem, $resolved, $quantity);

            $this->refreshPurchaseRequestProgress((int) $requestItem->request_id);
        });
    }

    public function syncOccupationForPurchaseOrderItem(DberpPurchaseOrderItem $orderItem): void
    {
        $orderItem->loadMissing('order');
        $order = $orderItem->order;

        if (! $order instanceof DberpPurchaseOrder) {
            return;
        }

        if ($order->status === PurchaseOrderStatus::Voided->value) {
            return;
        }

        if (! $this->orderItemHasSource($orderItem)) {
            $this->reverseOccupationForPurchaseOrderItem($orderItem, __('dberp.messages.purchase_conversion_source_removed'));

            return;
        }

        if ($order->status !== PurchaseOrderStatus::Draft->value) {
            return;
        }

        $activeLedger = $this->activeLedgerForPurchaseOrderItem($orderItem);
        $quantity = (float) $orderItem->quantity;

        if ($quantity <= 0) {
            if ($activeLedger !== null) {
                $this->reverseOccupationForPurchaseOrderItem($orderItem, __('dberp.messages.purchase_conversion_quantity_cleared'));
            }

            return;
        }

        if ($activeLedger === null) {
            $this->occupySourceForPurchaseOrderItem($order, $orderItem);

            return;
        }

        $ledgerQuantity = (float) $activeLedger->quantity;

        if (abs($ledgerQuantity - $quantity) <= 0.00005) {
            return;
        }

        DB::transaction(function () use ($order, $orderItem): void {
            $this->reverseOccupationForPurchaseOrderItem($orderItem, __('dberp.messages.purchase_conversion_quantity_adjusted'), false);
            $orderItem->refresh();
            $this->occupySourceForPurchaseOrderItem($order, $orderItem);
        });
    }

    public function reverseOccupationForPurchaseOrder(DberpPurchaseOrder $order, ?string $reason = null): void
    {
        $order->loadMissing('items');

        $requestIds = [];

        DB::transaction(function () use ($order, $reason, &$requestIds): void {
            foreach ($order->items as $item) {
                $requestId = $this->reverseOccupationForPurchaseOrderItem(
                    $item,
                    $reason ?? __('dberp.messages.purchase_conversion_order_voided'),
                    false,
                );

                if ($requestId !== null) {
                    $requestIds[$requestId] = true;
                }
            }
        });

        foreach (array_keys($requestIds) as $requestId) {
            $this->refreshPurchaseRequestProgress($requestId);
        }
    }

    public function reverseOccupationForPurchaseOrderItem(
        DberpPurchaseOrderItem $orderItem,
        ?string $reason = null,
        bool $refreshProgress = true,
    ): ?int {
        $ledgers = DberpPurchaseConversionLine::query()
            ->where('purchase_order_item_id', $orderItem->getKey())
            ->whereNull('reversed_at')
            ->lockForUpdate()
            ->get();

        if ($ledgers->isEmpty()) {
            return null;
        }

        $requestId = null;

        DB::transaction(function () use ($ledgers, $reason, $refreshProgress, &$requestId): void {
            foreach ($ledgers as $ledger) {
                if ($ledger->isReversed()) {
                    continue;
                }

                $quantity = (float) $ledger->quantity;

                if ($ledger->purchase_request_item_id !== null) {
                    $requestItem = DberpPurchaseRequestItem::query()
                        ->whereKey($ledger->purchase_request_item_id)
                        ->lockForUpdate()
                        ->first();

                    if ($requestItem instanceof DberpPurchaseRequestItem) {
                        $this->decrementRequestItemConvertedQuantity($requestItem, $quantity);
                        $requestId = (int) $requestItem->request_id;
                    }
                }

                $ledger->forceFill([
                    'reversed_at' => now(),
                    'reverse_reason' => $reason,
                    'updated_by' => Auth::id(),
                ])->save();
            }

            if ($refreshProgress && $requestId !== null) {
                $this->refreshPurchaseRequestProgress($requestId);
            }
        });

        return $requestId;
    }

    public function refreshPurchaseRequestProgress(int $purchaseRequestId): void
    {
        $request = DberpPurchaseRequest::query()->whereKey($purchaseRequestId)->first();

        if ($request === null) {
            return;
        }

        $this->purchaseRequestService->refreshTotals($request);
        $this->purchaseRequestService->syncProgressStatus($request->refresh());
    }

    public function assertNoDuplicateActiveQuote(int $inquiryItemId, int $supplierId, ?int $ignoreQuoteId = null): void
    {
        $exists = DberpPurchaseInquiryQuote::query()
            ->where('purchase_inquiry_item_id', $inquiryItemId)
            ->where('supplier_id', $supplierId)
            ->where('status', PurchaseInquiryQuoteStatus::Active->value)
            ->when($ignoreQuoteId !== null, fn (Builder $query): Builder => $query->whereKeyNot($ignoreQuoteId))
            ->exists();

        if ($exists) {
            throw ValidationException::withMessages([
                'supplier_id' => __('dberp.errors.duplicate_purchase_inquiry_quote'),
            ]);
        }
    }

    /**
     * @deprecated 使用 assertPurchaseRequestItemConvertible
     */
    public function validateSelectablePurchaseRequestItem(DberpPurchaseRequestItem $item, array $context = []): void
    {
        $quantity = isset($context['quantity']) ? (float) $context['quantity'] : $this->calculatePurchaseRequestItemRemainingQuantity($item);
        $this->assertPurchaseRequestItemConvertible($item, $quantity);
    }

    /**
     * @deprecated 使用 assertInquiryQuoteSelectable
     */
    public function validateSelectableInquiryQuote(DberpPurchaseInquiryQuote $quote, array $context = []): void
    {
        $supplierId = (int) ($context['supplier_id'] ?? $quote->supplier_id);
        $this->assertInquiryQuoteSelectable($quote, $supplierId);
    }

    /**
     * @return list<string>
     */
    private function convertiblePurchaseRequestStatuses(): array
    {
        return [
            PurchaseRequestStatus::Pending->value,
            PurchaseRequestStatus::PartiallyConverted->value,
        ];
    }

    private function orderItemHasSource(DberpPurchaseOrderItem $orderItem): bool
    {
        return $orderItem->purchase_request_item_id !== null
            || $orderItem->source_inquiry_quote_id !== null;
    }

    /**
     * @return array{
     *     purchase_request_item: DberpPurchaseRequestItem,
     *     purchase_inquiry_quote: ?DberpPurchaseInquiryQuote,
     *     purchase_inquiry_id: ?int,
     *     purchase_inquiry_item_id: ?int,
     *     source_type: string,
     *     source_header_id: int,
     *     source_line_id: int
     * }
     */
    private function resolveSourceForPurchaseOrderItem(DberpPurchaseOrder $order, DberpPurchaseOrderItem $orderItem): array
    {
        if ($orderItem->source_inquiry_quote_id !== null) {
            $quote = DberpPurchaseInquiryQuote::query()
                ->with(['purchaseRequestItem.request', 'purchaseInquiryItem'])
                ->whereKey($orderItem->source_inquiry_quote_id)
                ->firstOrFail();

            $this->assertInquiryQuoteSelectable($quote, (int) $order->supplier_id);

            $resolved = $this->resolveInquiryQuoteSource($quote);

            if ($orderItem->purchase_request_item_id === null) {
                $orderItem->forceFill([
                    'purchase_request_item_id' => $quote->purchase_request_item_id,
                ])->saveQuietly();
            } elseif ((int) $orderItem->purchase_request_item_id !== (int) $quote->purchase_request_item_id) {
                throw ValidationException::withMessages([
                    'source_inquiry_quote_id' => __('dberp.errors.purchase_order_source_inquiry_quote_mismatch'),
                ]);
            }

            return $resolved;
        }

        $requestItem = DberpPurchaseRequestItem::query()
            ->with('request')
            ->whereKey($orderItem->purchase_request_item_id)
            ->firstOrFail();

        return [
            'purchase_request_item' => $requestItem,
            'purchase_inquiry_quote' => null,
            'purchase_inquiry_id' => null,
            'purchase_inquiry_item_id' => null,
            'source_type' => PurchaseConversionDocumentType::PurchaseRequestItem->value,
            'source_header_id' => (int) $requestItem->request_id,
            'source_line_id' => (int) $requestItem->getKey(),
        ];
    }

    private function activeLedgerForPurchaseOrderItem(DberpPurchaseOrderItem $orderItem): ?DberpPurchaseConversionLine
    {
        return DberpPurchaseConversionLine::query()
            ->where('purchase_order_item_id', $orderItem->getKey())
            ->whereNull('reversed_at')
            ->first();
    }

    public function activeLedgerForInquiryQuote(int $quoteId): ?DberpPurchaseConversionLine
    {
        return DberpPurchaseConversionLine::query()
            ->where('purchase_inquiry_quote_id', $quoteId)
            ->whereNull('reversed_at')
            ->first();
    }

    /**
     * @param  array<string, mixed>  $filters
     * @return Builder<DberpPurchaseInquiryQuote>
     */
    public function selectableInquiryQuotesForConversionQuery(array $filters = []): Builder
    {
        $query = $this->selectableInquiryQuotesQuery($filters);

        return $query->whereNotExists(function (\Illuminate\Database\Query\Builder $subquery): void {
            $subquery
                ->selectRaw('1')
                ->from('dberp_purchase_conversion_lines')
                ->whereColumn('dberp_purchase_conversion_lines.purchase_inquiry_quote_id', 'dberp_purchase_inquiry_quotes.id')
                ->whereNull('dberp_purchase_conversion_lines.reversed_at');
        });
    }

    /**
     * @param  array{
     *     purchase_request_item: DberpPurchaseRequestItem,
     *     purchase_inquiry_quote: ?DberpPurchaseInquiryQuote,
     *     purchase_inquiry_id: ?int,
     *     purchase_inquiry_item_id: ?int,
     *     source_type: string,
     *     source_header_id: int,
     *     source_line_id: int
     * }  $resolved
     */
    private function createConversionLedger(
        DberpPurchaseOrder $order,
        DberpPurchaseOrderItem $orderItem,
        DberpPurchaseRequestItem $requestItem,
        array $resolved,
        float $quantity,
    ): DberpPurchaseConversionLine {
        $quote = $resolved['purchase_inquiry_quote'];

        return DberpPurchaseConversionLine::query()->create([
            'source_type' => $resolved['source_type'],
            'source_header_id' => $resolved['source_header_id'],
            'source_line_id' => $resolved['source_line_id'],
            'target_type' => PurchaseConversionDocumentType::PurchaseOrderItem->value,
            'target_header_id' => (int) $order->getKey(),
            'target_line_id' => (int) $orderItem->getKey(),
            'purchase_request_id' => (int) $requestItem->request_id,
            'purchase_request_item_id' => (int) $requestItem->getKey(),
            'purchase_inquiry_id' => $resolved['purchase_inquiry_id'],
            'purchase_inquiry_item_id' => $resolved['purchase_inquiry_item_id'],
            'purchase_inquiry_quote_id' => $quote?->getKey(),
            'purchase_order_id' => (int) $order->getKey(),
            'purchase_order_item_id' => (int) $orderItem->getKey(),
            'supplier_id' => (int) $order->supplier_id,
            'product_id' => (int) $orderItem->product_id,
            'unit_id' => $orderItem->unit_id !== null ? (int) $orderItem->unit_id : null,
            'quantity' => $this->formatQuantity($quantity),
            'source_snapshot' => $this->buildSourceSnapshot($order, $orderItem, $requestItem, $quote),
            'converted_at' => now(),
            'created_by' => Auth::id(),
            'updated_by' => Auth::id(),
        ]);
    }

    /**
     * @return array<string, mixed>
     */
    private function buildSourceSnapshot(
        DberpPurchaseOrder $order,
        DberpPurchaseOrderItem $orderItem,
        DberpPurchaseRequestItem $requestItem,
        ?DberpPurchaseInquiryQuote $quote,
    ): array {
        return [
            'purchase_request_code' => $requestItem->request?->code,
            'purchase_order_code' => $order->code,
            'purchase_order_item_id' => $orderItem->getKey(),
            'inquiry_quote_code' => $quote?->code,
            'product_id' => $orderItem->product_id,
            'product_sku_id' => $orderItem->product_sku_id,
        ];
    }

    private function incrementRequestItemConvertedQuantity(DberpPurchaseRequestItem $requestItem, float $quantity): void
    {
        $requestItem->forceFill([
            'converted_quantity' => $this->formatQuantity((float) $requestItem->converted_quantity + $quantity),
        ])->save();
    }

    private function decrementRequestItemConvertedQuantity(DberpPurchaseRequestItem $requestItem, float $quantity): void
    {
        $next = max((float) $requestItem->converted_quantity - $quantity, 0);

        $requestItem->forceFill([
            'converted_quantity' => $this->formatQuantity($next),
        ])->save();
    }

    private function formatQuantity(float $quantity): string
    {
        return number_format($quantity, 4, '.', '');
    }
}
