=== CURRENT ZATCA SALES RETURN PATH ===

FILE=app/Services/Zatca/FatooraInvoiceService.php
FILE_FOUND=YES

====================================================================================================
METHOD=submitSalesReturn
====================================================================================================
    public function submitSalesReturn(ReturnProduct $return): array
    {
        if (Setting::findByKey('zatca_enabled', '0') != '1') {
            return ['success' => false, 'message' => 'ZATCA integration is disabled in settings.'];
        }
        if ($return->return_type !== 'sales') {
            return ['success' => false, 'message' => 'Only sales returns can be submitted as ZATCA credit notes.'];
        }
        if ($return->zatca_submitted_at) {
            return ['success' => false, 'message' => 'Return was already submitted to ZATCA.'];
        }
        if (!$return->order_id) {
            return ['success' => false, 'message' => 'Link the return to the original sales invoice before ZATCA submission.'];
        }

        return DB::transaction(function () use ($return) {
            /** @var ReturnProduct $return */
            $return = ReturnProduct::query()->whereKey($return->id)->lockForUpdate()->first();
            if (!$return) {
                return ['success' => false, 'message' => 'Return not found.'];
            }
            $return->load(['order.client', 'details']);

            $parent = $return->order;
            if (!$parent || !$parent->zatca_submitted_at) {
                return ['success' => false, 'message' => 'The original sales invoice must be sent to ZATCA before this credit note.'];
            }

            /** @var ZatcaCredential|null $credential */
            $credential = ZatcaCredential::query()->where('is_active', 1)->lockForUpdate()->first();
            if (!$credential || !$credential->private_key || !$credential->certificate || !$credential->secret) {
                return ['success' => false, 'message' => 'No active ZATCA credentials configured.'];
            }

            $nextDoc = (new ZatcaSubmissionQueue())->getNextPending();
            if (!$nextDoc || $nextDoc['type'] !== 'return' || (int) $nextDoc['model']->id !== (int) $return->id) {
                throw new ZatcaSubmissionOutOfOrderException(
                    'يجب إرسال الوثائق بالترتيب — توجد فاتورة أو مردود سابق لم يُرسل إلى الهيئة بعد.'
                );
            }

            config(['zatca.app.environment' => $credential->environment]);

            $seller = $this->makeSeller($credential);
            $invoiceItems = $this->buildReturnInvoiceItems($return);
            if (count($invoiceItems) === 0) {
                return ['success' => false, 'message' => 'Return has no line items.'];
            }

            $aggregates = $this->aggregateItems($invoiceItems);
            $taxPercent = (float) ($return->tax ?? 0);
            if ($taxPercent > 0 && !in_array($taxPercent, [5.0, 15.0], true)) {
                return ['success' => false, 'message' => "Invalid VAT rate {$taxPercent}% — KSA Standard Rate must be 5% or 15% (BR-KSA-84)."];
            }
            $invoiceUuid = $return->zatca_invoice_uuid ?: (string) Str::uuid();
            if (!$return->zatca_invoice_uuid) {
                $return->zatca_invoice_uuid = $invoiceUuid;
                $return->save();
            }

            $returnCreatedAtKsa = $return->created_at->copy()->setTimezone('Asia/Riyadh');
            $invoiceDate = $return->return_date
                ? \Carbon\Carbon::parse($return->return_date)->toDateString()
                : $returnCreatedAtKsa->toDateString();
            $invoiceTime = $returnCreatedAtKsa->format('H:i:s');
            $previousHash = $credential->last_invoice_hash ?: null;

            $creditNumber = 'CR-' . $return->id;
            $billingRef = (string) $parent->invoice_number;
            $invoiceNote = 'Credit note for sales return #' . $return->id . ' referencing invoice ' . $billingRef;

            $invoice = new Invoice(
                (int) (self::CREDIT_NOTE_ICV_OFFSET + (int) $return->id),
                $creditNumber,
                $invoiceUuid,
                $invoiceDate,
                $invoiceTime,
                InvoiceType::CREDIT_NOTE,
                $this->mapPaymentType($parent->payment_type),
                $aggregates['price'],
                $aggregates['discount'],
                $aggregates['tax'],
                $aggregates['total'],
                $invoiceItems,
                $previousHash,
                $billingRef,
                $invoiceNote,
                'return #' . $return->id,
                'SAR',
                $taxPercent,
                $invoiceDate
            );

            // TESTMOBILE_ZATCA_DOCUMENT_TOTALS_BT115_FIX_20260720
            $invoice->prepaid_amount = 0.0;
            $invoice->payable_amount = round(
                (float) (
                    $return->total
                    ?: $return->return_value
                ),
                2
            );
            $invoice->payable_rounding_amount = round(
                $invoice->payable_amount
                    - (float) $invoice->total,
                2
            );

            $clientPerson = $parent->client;
            $useB2b = $this->isB2BClient($clientPerson);

            if ($useB2b) {
                $fatooraClient = $this->makeClient($clientPerson);
                $reporter = B2B::make($seller, $invoice, $fatooraClient)->report();
            } else {
                $reporter = B2C::make($seller, $invoice)->report();
            }

            $assessment = $this->assessSubmissionResult($reporter);

            $validationStatus = $assessment['validation_status'];
            $reportingStatus = $assessment['reporting_status'];

            $return->zatca_qr_payload = $assessment['qr'];
            $return->zatca_signed_xml = $assessment['xml'];
            $return->zatca_reporting_status = $reportingStatus;
            $return->zatca_validation_status = $validationStatus;
            $return->zatca_invoice_hash = $assessment['invoice_hash'];
            $return->zatca_raw_response = [
                'validation' => $assessment['validation'],
                'info' => $assessment['info'],
                'warnings' => $assessment['warnings'],
                'errors' => $assessment['errors'],
                'classification' => $assessment['classification'],
                'strict_pass' => $assessment['strict_pass'],
                'accepted' => $assessment['accepted'],
                'artifacts_complete' => $assessment['artifacts_complete'],
            ];

            if ($assessment['accepted']) {
                /*
                 * Accepted credit notes must remain in the local PIH chain,
                 * including documents accepted with validation warnings.
                 */
                $return->zatca_submitted_at = now();
                $return->save();

                if ($assessment['invoice_hash'] !== '') {
                    $credential->last_invoice_hash =
                        $assessment['invoice_hash'];
                    $credential->save();
                }

                if ($assessment['xml'] !== '') {
                    $this->saveXmlFile(
                        $useB2b
                            ? 'standard'
                            : 'simplified/credit',
                        'CR-' . $return->id,
                        $assessment['xml']
                    );
                }
            } else {
                $return->zatca_submitted_at = null;
                $return->save();
            }

            return [
                'success' => $assessment['accepted'],
                'accepted' => $assessment['accepted'],
                'strict_pass' => $assessment['strict_pass'],
                'classification' => $assessment['classification'],
                'reporting_status' => $reportingStatus,
                'validation_status' => $validationStatus,
                'warnings_count' => count($assessment['warnings']),
                'errors_count' => count($assessment['errors']),
                'message' => $assessment['message'],
            ];
        });
    }

====================================================================================================
METHOD=buildReturnInvoiceItems
====================================================================================================
    protected function buildReturnInvoiceItems(ReturnProduct $return): array
    {
        $return->loadMissing('details');
        $taxPercent = (float) ($return->tax ?? 0);
        $taxCategoryCode = $taxPercent > 0 ? TaxCategoryCode::STANDARD_RATE : TaxCategoryCode::ZERO_RATE;
        $items = [];
        $lineNo = 1;

        foreach ($return->details as $product) {
            $pivot = $product->pivot;
            $qty = (float) $pivot->qty;
            if ($qty <= 0) {
                continue;
            }

            $lineTotalInclTax = round((float) $pivot->price * $qty, 4);
            $rate = $taxPercent > 0 ? ($taxPercent / 100) : 0;
            $lineTax = $rate > 0 ? round($lineTotalInclTax - ($lineTotalInclTax / (1 + $rate)), 4) : 0.0;
            $lineNet = round($lineTotalInclTax - $lineTax, 4);
            $unitNet = $qty > 0 ? round($lineNet / $qty, 12) : 0.0;
            $name = $pivot->product_name ?: $product->name;

            $items[] = new InvoiceItem(
                $lineNo++,
                (string) $name,
                (int) round($qty),
                (float) $unitNet,
                0.0,
                (float) $lineTax,
                (float) $taxPercent,
                (float) $lineTotalInclTax,
                null,
                $taxCategoryCode
            );
        }

        return $items;
    }

====================================================================================================
METHOD=aggregateItems
====================================================================================================
    protected function aggregateItems(array $invoiceItems): array
    {
        // TESTMOBILE_ZATCA_DOCUMENT_TOTALS_BT115_FIX_20260720
        // BT-106 and BT-116 must be based on the sum of
        // individually rounded invoice line net amounts (BT-131).
        $price = 0.0;
        $discount = 0.0;
        $taxGroups = [];

        foreach ($invoiceItems as $item) {
            $lineNet = round((float) $item->sub_total, 2);
            $lineDiscount = round((float) $item->discount, 2);
            $taxPercent = (float) $item->tax_percent;
            $taxCategory = (string) $item->tax_category_code;

            $price = round($price + $lineNet, 2);
            $discount = round(
                $discount + $lineDiscount,
                2
            );

            $groupKey = $taxCategory
                . '|'
                . number_format(
                    $taxPercent,
                    6,
                    '.',
                    ''
                );

            if (!isset($taxGroups[$groupKey])) {
                $taxGroups[$groupKey] = [
                    'taxable' => 0.0,
                    'rate' => $taxPercent,
                ];
            }

            $taxGroups[$groupKey]['taxable'] = round(
                $taxGroups[$groupKey]['taxable']
                    + $lineNet,
                2
            );
        }

        $tax = 0.0;

        foreach ($taxGroups as $group) {
            $groupTax = $group['rate'] > 0
                ? round(
                    $group['taxable']
                    * ($group['rate'] / 100),
                    2
                )
                : 0.0;

            $tax = round($tax + $groupTax, 2);
        }

        // BT-112 = BT-109 + BT-110.
        $total = round($price + $tax, 2);

        return [
            'price' => round($price, 2),
            'discount' => round($discount, 2),
            'tax' => round($tax, 2),
            'total' => round($total, 2),
        ];
    }

====================================================================================================
METHOD=resolveReturnPaymentMeansCode
====================================================================================================
METHOD_FOUND=NO

====================================================================================================
METHOD=resolvePaymentMeansCode
====================================================================================================
METHOD_FOUND=NO

=== INVOICE ITEM CLASS ===
KNOWN_PATHS_FOUND=NO

=== INVOICE ITEM SEARCH RESULTS ===
app/Services/Zatca/FatooraInvoiceService.php
fatoora-zatca/src/Objects/InvoiceItem.php

=== RETURN XML TEMPLATE REFERENCES ===
fatoora-zatca/src/Services/Invoice/HashInvoiceService.php
fatoora-zatca/src/Services/Invoice/XmlInvoiceItemsService.php
fatoora-zatca/src/Xml/xml_billing_reference.xml
fatoora-zatca/src/Xml/xml_line_item.xml
fatoora-zatca/src/Xml/xml_line_item_discount.xml
fatoora-zatca/src/Xml/xml_signed.xml
fatoora-zatca/src/Xml/xml_to_hash.xml
fatoora-zatca/src/Xml/xml_to_hash_OLD.xml

FILES_CHANGED=NO
DATABASE_CHANGED=NO
ZATCA_REQUEST_SENT=NO